我希望某个操作能在一定时间内执行。当时间到期后,发送另一个执行命令。
StartDoingStuff();
System.Threading.Thread.Sleep(200);
StopDoingStuff();
如何使用C#中的Async/Task/Await来编写代码,而不是在其中放置一个阻止应用程序其余部分的睡眠语句?
我希望某个操作能在一定时间内执行。当时间到期后,发送另一个执行命令。
StartDoingStuff();
System.Threading.Thread.Sleep(200);
StopDoingStuff();
如何使用C#中的Async/Task/Await来编写代码,而不是在其中放置一个阻止应用程序其余部分的睡眠语句?
2011年,Joe Hoag在Parallel Team的博客中回答了这个问题:Crafting a Task.TimeoutAfter Method。
该解决方案使用了TaskCompletionSource并包含了几个优化(通过避免捕获可实现12%的性能提升),处理了清理和覆盖调用已经完成的目标任务、传递无效超时等边缘情况。
Task.TimeoutAfter的美妙之处在于它非常容易与其他延续组合,因为它只做一件事:通知您超时已过期。它不会尝试取消您的任务。当抛出TimeoutException时,您可以决定如何处理该异常。
Stephen Toub提供了一个使用async/await
实现的快速方案,虽然没有涵盖那么多边缘情况。
优化后的实现代码如下:
public static Task TimeoutAfter(this Task task, int millisecondsTimeout)
{
// Short-circuit #1: infinite timeout or task already completed
if (task.IsCompleted || (millisecondsTimeout == Timeout.Infinite))
{
// Either the task has already completed or timeout will never occur.
// No proxy necessary.
return task;
}
// tcs.Task will be returned as a proxy to the caller
TaskCompletionSource<VoidTypeStruct> tcs =
new TaskCompletionSource<VoidTypeStruct>();
// Short-circuit #2: zero timeout
if (millisecondsTimeout == 0)
{
// We've already timed out.
tcs.SetException(new TimeoutException());
return tcs.Task;
}
// Set up a timer to complete after the specified timeout period
Timer timer = new Timer(state =>
{
// Recover your state information
var myTcs = (TaskCompletionSource<VoidTypeStruct>)state;
// Fault our proxy with a TimeoutException
myTcs.TrySetException(new TimeoutException());
}, tcs, millisecondsTimeout, Timeout.Infinite);
// Wire up the logic for what happens when source task completes
task.ContinueWith((antecedent, state) =>
{
// Recover our state data
var tuple =
(Tuple<Timer, TaskCompletionSource<VoidTypeStruct>>)state;
// Cancel the Timer
tuple.Item1.Dispose();
// Marshal results to proxy
MarshalTaskResults(antecedent, tuple.Item2);
},
Tuple.Create(timer, tcs),
CancellationToken.None,
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Default);
return tcs.Task;
}
以及 Stephen Toub 的实现,没有考虑边缘情况:
public static async Task TimeoutAfter(this Task task, int millisecondsTimeout)
{
if (task == await Task.WhenAny(task, Task.Delay(millisecondsTimeout)))
await task;
else
throw new TimeoutException();
}
await StartDoingStuff();
await Task.Delay(200);
await StopDoingStuff();
编辑: 如果原问题提出者想要一个异步方法,在特定时间后取消:假设该方法不会进行任何网络请求,只是在内存中进行一些处理,并且可以随意终止结果而不考虑其影响,则使用取消令牌。
private async Task Go()
{
CancellationTokenSource source = new CancellationTokenSource();
source.CancelAfter(200);
await Task.Run(() => DoIt(source.Token));
}
private void DoIt(CancellationToken token)
{
while (true)
{
token.ThrowIfCancellationRequested();
}
}
编辑: 我应该提到,您可以捕获结果为OperationCanceledException的异常,以提供任务结束方式的指示,从而避免处理布尔值的需要。CancellationTokenSource
构造函数 设置超时时间。// return true if the job has been done, false if cancelled
async Task<bool> DoSomethingWithTimeoutAsync(int timeout)
{
var tokenSource = new CancellationTokenSource(timeout);
CancellationToken ct = tokenSource.Token;
var doSomethingTask = Task<bool>.Factory.StartNew(() =>
{
Int64 c = 0; // count cycles
bool moreToDo = true;
while (moreToDo)
{
if (ct.IsCancellationRequested)
return false;
// Do some useful work here: counting
Debug.WriteLine(c++);
if (c > 100000)
moreToDo = false; // done counting
}
return true;
}, tokenSource.Token);
return await doSomethingTask;
}
private async void Form1_Load(object sender, EventArgs e)
{
bool result = await DoSomethingWithTimeoutAsync(3000);
MessageBox.Show("DoSomethingWithTimeout done:" + result); // false if cancelled
}
private void Form1_Load(object sender, EventArgs e)
{
Task<bool> task = DoSomethingWithTimeoutAsync(3000);
task.ContinueWith(_ =>
{
MessageBox.Show("DoSomethingWithTimeout done:" + task.Result); // false is cancelled
}, TaskScheduler.FromCurrentSynchronizationContext());
}
DoSomethingWithTimeoutAsync
中,所以出于简单起见,我选择不使用异常。 - noseratio - open to work
async
-await
的问题,这是C# 5.0的新功能,但是你的问题标记为C# 4.0。那么,到底是哪一个版本呢? - svickStartDoingStuff(); Sleep(200); StopDoingStuff(); Sleep(200); ResumeDoingStuff(); /* etc.. */
? - noseratio - open to work