cancellationtoken timeout vs task.delay()和timeout

时间:2014-05-05 15:45:29

标签: .net task-parallel-library

我想运行一个应该在n毫秒后超时的操作。我已经用两种方式实现了它,一种是在等待n毫秒后自己取消操作,另一种是在n毫秒后传递一个CancellationToken设置到期。

我担心当我的系统负载过重时,canceltoken可能会在操作开始之前到期。看来如果我自己使用Task.Delay()实现超时,那么在我的操作开始之后,Delay()调用才会运行。

以下是我的表现:

public static async Task<T> TimeoutAfter<T>(this Task<T> task, TimeSpan timeout)
    {
        Task completedTask = await Task.WhenAny(task, Task.Delay(timeout));
        if (completedTask == task)
        {
            return await task;
        }

        throw new TimeoutException();
    }

// Use it like this
await SomeOperationAsync().TimeoutAfter(TimeSpan.FromMilliseconds(n));

与:相比:

CancellationTokenSource source = new CancellationTokenSource(TimeSpan.FromMilliseconds(n));
await SomeOperationAsync(source.Token);

1 个答案:

答案 0 :(得分:22)

我不确定你的担心是否合理,但我会假设它是。

使用Task.Delay()的代码存在的问题是您实际上并没有取消操作。这可能意味着您浪费资源或误导您的用户(您告诉他们操作超时,而操作仍在运行并且很可能成功完成)。

现在,如果您想确保取消令牌仅在操作开始后才开始计时,那么执行

var source = new CancellationTokenSource();
var task = SomeOperationAsync(source.Token);
source.CancelAfter(TimeSpan.FromMilliseconds(n));
await task;

如果经常这样做,您可能希望将此逻辑封装到方法中(可能需要更好的名称):

public static async Task WithTimeoutAfterStart(
    Func<CancellationToken, Task> operation, TimeSpan timeout)
{
    var source = new CancellationTokenSource();
    var task = operation(source.Token);
    source.CancelAfter(timeout);
    await task;
}

用法:

await WithTimeoutAfterStart(
    ct => SomeOperationAsync(ct), TimeSpan.FromMilliseconds(n));