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

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


当前回答

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

TimeSpan timeout = TimeSpan.FromSeconds(10);

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

TaskCanceledException将在超时时抛出。

其他回答

解决这个问题的另一种方法是使用响应式扩展:

public static Task TimeoutAfter(this Task task, TimeSpan timeout, IScheduler scheduler)
{
        return task.ToObservable().Timeout(timeout, scheduler).ToTask();
}

测试上面使用下面的代码在你的单元测试,它为我工作

TestScheduler scheduler = new TestScheduler();
Task task = Task.Run(() =>
                {
                    int i = 0;
                    while (i < 5)
                    {
                        Console.WriteLine(i);
                        i++;
                        Thread.Sleep(1000);
                    }
                })
                .TimeoutAfter(TimeSpan.FromSeconds(5), scheduler)
                .ContinueWith(t => { }, TaskContinuationOptions.OnlyOnFaulted);

scheduler.AdvanceBy(TimeSpan.FromSeconds(6).Ticks);

您可能需要以下命名空间:

using System.Threading.Tasks;
using System.Reactive.Subjects;
using System.Reactive.Linq;
using System.Reactive.Threading.Tasks;
using Microsoft.Reactive.Testing;
using System.Threading;
using System.Reactive.Concurrency;

这个怎么样:

int timeout = 1000;
var task = SomeOperationAsync();
if (await Task.WhenAny(task, Task.Delay(timeout)) == task) {
    // task completed within timeout
} else { 
    // timeout logic
}

这里有一篇很棒的博客文章“制定任务”。TimeoutAfter Method”(来自MS并行库团队)提供了关于这类事情的更多信息。

补充:在我的回答的评论请求,这里是一个扩展的解决方案,包括取消处理。请注意,将取消传递给任务和计时器意味着在代码中可以经历多种取消方式,您应该确保测试并确信您正确地处理了所有这些方法。不要让各种组合的机会,并希望您的计算机在运行时做正确的事情。

int timeout = 1000;
var task = SomeOperationAsync(cancellationToken);
if (await Task.WhenAny(task, Task.Delay(timeout, cancellationToken)) == task)
{
    // Task completed within timeout.
    // Consider that the task may have faulted or been canceled.
    // We re-await the task so that any exceptions/cancellation is rethrown.
    await task;

}
else
{
    // timeout/cancellation logic
}

上面@Kevan的答案的通用版本,使用响应式扩展。

public static Task<T> TimeoutAfter<T>(this Task<T> task, TimeSpan timeout, IScheduler scheduler)
{
    return task.ToObservable().Timeout(timeout, scheduler).ToTask();
}

可选调度器:

public static Task<T> TimeoutAfter<T>(this Task<T> task, TimeSpan timeout, Scheduler scheduler = null)
{
    return scheduler is null 
       ? task.ToObservable().Timeout(timeout).ToTask() 
       : task.ToObservable().Timeout(timeout, scheduler).ToTask();
}

BTW:当Timeout发生时,将抛出一个超时异常

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

TimeSpan timeout = TimeSpan.FromSeconds(10);

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

TaskCanceledException将在超时时抛出。

在。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`");
}