取消令牌超时与任务延迟和超时

15

我想要运行一个操作,这个操作在n毫秒后应该超时。我有两种实现方式,一种是在等待n毫秒后自己取消操作,另一种是传入一个CancellationToken,在n毫秒后自动过期。

我担心在系统负载过重的情况下,Cancellation Token 可能会在操作开始之前就过期。如果我使用 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);

在操作开始之前就使CancellationTokenSource过期,这是一个非常沉重的负担。 - paparazzo
1个回答

28

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

使用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));

CancellationTokenSource source 应该放在 using 块中吗? - Dai

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接