我想等待一个任务<T>完成一些特殊的规则: 如果在X毫秒后还没有完成,我希望向用户显示一条消息。 如果在Y毫秒后还没有完成,我想自动请求取消。
我可以使用Task。ContinueWith异步等待任务完成(即计划在任务完成时执行一个操作),但不允许指定超时。 我可以使用Task。等待同步等待任务超时完成,但这会阻塞我的线程。 我如何异步等待任务超时完成?
我想等待一个任务<T>完成一些特殊的规则: 如果在X毫秒后还没有完成,我希望向用户显示一条消息。 如果在Y毫秒后还没有完成,我想自动请求取消。
我可以使用Task。ContinueWith异步等待任务完成(即计划在任务完成时执行一个操作),但不允许指定超时。 我可以使用Task。等待同步等待任务超时完成,但这会阻塞我的线程。 我如何异步等待任务超时完成?
当前回答
上面@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发生时,将抛出一个超时异常
其他回答
我觉得task . delay()任务和CancellationTokenSource在另一个紧密的网络循环中回答了我的用例。
尽管乔·霍格的《制作任务》MSDN博客上的TimeoutAfter方法是鼓舞人心的,出于同样的原因,我对使用TimeoutException进行流控制有点厌倦,因为超时比不超时更频繁。
所以我使用了这个,它也处理了博客中提到的优化:
public static async Task<bool> BeforeTimeout(this Task task, int millisecondsTimeout)
{
if (task.IsCompleted) return true;
if (millisecondsTimeout == 0) return false;
if (millisecondsTimeout == Timeout.Infinite)
{
await Task.WhenAll(task);
return true;
}
var tcs = new TaskCompletionSource<object>();
using (var timer = new Timer(state => ((TaskCompletionSource<object>)state).TrySetCanceled(), tcs,
millisecondsTimeout, Timeout.Infinite))
{
return await Task.WhenAny(task, tcs.Task) == task;
}
}
一个示例用例如下:
var receivingTask = conn.ReceiveAsync(ct);
while (!await receivingTask.BeforeTimeout(keepAliveMilliseconds))
{
// Send keep-alive
}
// Read and do something with data
var data = await receivingTask;
像这样的东西怎么样?
const int x = 3000;
const int y = 1000;
static void Main(string[] args)
{
// Your scheduler
TaskScheduler scheduler = TaskScheduler.Default;
Task nonblockingTask = new Task(() =>
{
CancellationTokenSource source = new CancellationTokenSource();
Task t1 = new Task(() =>
{
while (true)
{
// Do something
if (source.IsCancellationRequested)
break;
}
}, source.Token);
t1.Start(scheduler);
// Wait for task 1
bool firstTimeout = t1.Wait(x);
if (!firstTimeout)
{
// If it hasn't finished at first timeout display message
Console.WriteLine("Message to user: the operation hasn't completed yet.");
bool secondTimeout = t1.Wait(y);
if (!secondTimeout)
{
source.Cancel();
Console.WriteLine("Operation stopped!");
}
}
});
nonblockingTask.Start();
Console.WriteLine("Do whatever you want...");
Console.ReadLine();
}
您可以使用任务。等待选项,不阻塞主线程使用另一个任务。
你可以使用Task。WaitAny用于等待多个任务中的第一个。
您可以创建两个额外的任务(在指定的超时后完成),然后使用WaitAny等待先完成的任务。如果最先完成的任务是你的“工作”任务,那么你就完成了。如果最先完成的任务是一个超时任务,那么您可以对超时做出反应(例如,请求取消)。
为了它的乐趣,我做了一个“OnTimeout”扩展任务。超时时Task执行所需的内联lambda Action()并返回true,否则返回false。
public static async Task<bool> OnTimeout<T>(this T t, Action<T> action, int waitms) where T : Task
{
if (!(await Task.WhenAny(t, Task.Delay(waitms)) == t))
{
action(t);
return true;
} else {
return false;
}
}
OnTimeout扩展返回一个bool结果,可以分配给一个变量,就像这个例子调用UDP套接字Async:
var t = UdpSocket.ReceiveAsync();
var timeout = await t.OnTimeout(task => {
Console.WriteLine("No Response");
}, 5000);
在timeout lambda中可以访问“task”变量以进行更多处理。
Action接收对象的使用可能会启发其他各种扩展设计。
在。net 6中(这个答案的日期是预览7),可以使用新的WaitAsync(TimeSpan, CancellationToken)来满足这个特殊的需求。 如果你可以使用。net 6,如果我们将这个版本与本文中提出的大多数好的解决方案进行比较,这个版本将被描述为优化。
(感谢所有参与者,因为我多年来一直使用您的解决方案)