我希望给定的操作执行一段时间。当该时间到期时,发送另一个执行命令。
StartDoingStuff();
System.Threading.Thread.Sleep(200);
StopDoingStuff();
如果在那里有一个阻塞应用程序其余部分的sleep语句,我怎么能用C#中的Async / Task / Await写这个呢?
答案 0 :(得分:10)
2011年并行团队的博客中,Joe Hoag回答了这个问题:Crafting a Task.TimeoutAfter Method。
该解决方案使用TaskCompletionSource并包含多个优化(12%仅通过避免捕获),处理清理并涵盖边缘情况,例如在目标任务完成时调用TimeoutAfter,传递无效超时等。
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();
}
答案 1 :(得分:3)
假设StartDoingStuff和StopDoingStuff已创建为Async方法返回Task然后
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,提供关于任务如何结束的指示,避免乱搞bool。
答案 2 :(得分:2)
以下是我使用task cancellation pattern(没有抛出异常的选项)的方法。
[已审核] 已更新,使用Svick的建议通过CancellationTokenSource
constructor设置超时。
// 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());
}