我想等待一个任务<T>完成一些特殊的规则: 如果在X毫秒后还没有完成,我希望向用户显示一条消息。 如果在Y毫秒后还没有完成,我想自动请求取消。

我可以使用Task。ContinueWith异步等待任务完成(即计划在任务完成时执行一个操作),但不允许指定超时。 我可以使用Task。等待同步等待任务超时完成,但这会阻塞我的线程。 我如何异步等待任务超时完成?


当前回答

在。net 6中(这个答案的日期是预览7),可以使用新的WaitAsync(TimeSpan, CancellationToken)来满足这个特殊的需求。 如果你可以使用。net 6,如果我们将这个版本与本文中提出的大多数好的解决方案进行比较,这个版本将被描述为优化。

(感谢所有参与者,因为我多年来一直使用您的解决方案)

其他回答

所以这是古老的,但有一个更好的现代解决方案。不确定c#/的哪个版本。NET是必需的,但这是我怎么做的:


... Other method code not relevant to the question.

// a token source that will timeout at the specified interval, or if cancelled outside of this scope
using var timeoutTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(5));
using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(token, timeoutTokenSource.Token);

async Task<MessageResource> FetchAsync()
{
    try
    {
        return await MessageResource.FetchAsync(m.Sid);
    } catch (TaskCanceledException e)
    {
        if (timeoutTokenSource.IsCancellationRequested)
            throw new TimeoutException("Timeout", e);
        throw;
    }
}

return await Task.Run(FetchAsync, linkedTokenSource.Token);

CancellationTokenSource构造函数接受一个TimeSpan参数,该参数将导致令牌在该间隔结束后取消。然后,您可以将异步(或者同步)代码包装到另一个Task调用中。运行,传递超时令牌。

这假设您正在传递一个取消令牌(令牌变量)。如果不需要在超时后单独取消任务,则可以直接使用timeoutTokenSource。否则,您将创建linkedTokenSource,它将在超时发生或以其他方式取消时取消。

然后,我们只捕获OperationCancelledException并检查是哪个令牌抛出了异常,如果超时导致引发异常,则抛出TimeoutException。否则,我们重新抛出。

此外,我在这里使用的是c# 7中引入的局部函数,但您可以很容易地使用lambda或实际函数来达到同样的效果。类似地,c# 8为使用语句引入了更简单的语法,但这些语法很容易重写。

使用Stephen Cleary的优秀AsyncEx库,你可以做到:

TimeSpan timeout = TimeSpan.FromSeconds(10);

using (var cts = new CancellationTokenSource(timeout))
{
    await myTask.WaitAsync(cts.Token);
}

TaskCanceledException将在超时时抛出。

创建一个扩展来等待任务或延迟完成,以先发生者为准。如果延迟成功,则抛出异常。

public static async Task<TResult> WithTimeout<TResult>(this Task<TResult> task, TimeSpan timeout)
{
    if (await Task.WhenAny(task, Task.Delay(timeout)) != task)
        throw new TimeoutException();
    return await task;
}

在。net 6 (Preview 7)或更高版本中,有一个新的内置方法Task。WaitAsync来实现这一点。

// Using TimeSpan
await myTask.WaitAsync(TimeSpan.FromSeconds(10));

// Using CancellationToken
await myTask.WaitAsync(cancellationToken);

// Using both TimeSpan and CancellationToken
await myTask.WaitAsync(TimeSpan.FromSeconds(10), cancellationToken);

如果任务在TimeSpan或CancellationToken之前没有完成,那么它会分别抛出TimeoutException或TaskCanceledException

try
{
    await myTask.WaitAsync(TimeSpan.FromSeconds(10), cancellationToken);

}
catch (TaskCanceledException)
{
    Console.WriteLine("Task didn't get finished before the `CancellationToken`");
}
catch (TimeoutException)
{
    Console.WriteLine("Task didn't get finished before the `TimeSpan`");
}

在。net 6中(这个答案的日期是预览7),可以使用新的WaitAsync(TimeSpan, CancellationToken)来满足这个特殊的需求。 如果你可以使用。net 6,如果我们将这个版本与本文中提出的大多数好的解决方案进行比较,这个版本将被描述为优化。

(感谢所有参与者,因为我多年来一直使用您的解决方案)