我正在玩这些Windows 8 WinRT任务,我正在尝试使用下面的方法取消任务,并且它在某种程度上起作用。 CancelNotification方法被调用,这使您认为任务被取消,但在后台任务继续运行,然后在完成后,任务的状态始终完成并且永远不会被取消。有没有办法在取消任务时完全停止任务?
private async void TryTask()
{
CancellationTokenSource source = new CancellationTokenSource();
source.Token.Register(CancelNotification);
source.CancelAfter(TimeSpan.FromSeconds(1));
var task = Task<int>.Factory.StartNew(() => slowFunc(1, 2), source.Token);
await task;
if (task.IsCompleted)
{
MessageDialog md = new MessageDialog(task.Result.ToString());
await md.ShowAsync();
}
else
{
MessageDialog md = new MessageDialog("Uncompleted");
await md.ShowAsync();
}
}
private int slowFunc(int a, int b)
{
string someString = string.Empty;
for (int i = 0; i < 200000; i++)
{
someString += "a";
}
return a + b;
}
private void CancelNotification()
{
}
答案 0 :(得分:206)
阅读Cancellation(在.NET 4.0中引入并且从那时起基本保持不变)和Task-Based Asynchronous Pattern,它提供了有关如何将CancellationToken
与{{1}一起使用的指南方法。
总而言之,您将async
传递给支持取消的每个方法,并且该方法必须定期检查。
CancellationToken
答案 1 :(得分:30)
或者,为了避免修改slowFunc
(例如,您无法访问源代码):
var source = new CancellationTokenSource(); //original code
source.Token.Register(CancelNotification); //original code
source.CancelAfter(TimeSpan.FromSeconds(1)); //original code
var completionSource = new TaskCompletionSource<object>(); //New code
source.Token.Register(() => completionSource.TrySetCanceled()); //New code
var task = Task<int>.Factory.StartNew(() => slowFunc(1, 2), source.Token); //original code
//original code: await task;
await Task.WhenAny(task, completionSource.Task); //New code
你也可以使用https://github.com/StephenCleary/AsyncEx中的漂亮扩展方法,看起来很简单:
await Task.WhenAny(task, source.Token.AsTask());
答案 2 :(得分:7)
尚未涉及的一种情况是如何在异步方法内部处理取消。以一个简单的案例为例,您需要将一些数据上传到服务,然后再获取它来计算一些东西,然后返回一些结果。
public async Task<Results> ProcessDataAsync(MyData data)
{
var client = await GetClientAsync();
await client.UploadDataAsync(data);
await client.CalculateAsync();
return await client.GetResultsAsync();
}
如果要支持取消,那么最简单的方法是传递令牌,并检查每个异步方法调用之间是否已将其取消(或使用ContinueWith)。如果通话时间很长,尽管您可能需要等待一段时间才能取消。我创建了一个小助手方法,但取消后立即失败了。
public static class TaskExtensions
{
public static async Task<T> WaitOrCancel<T>(this Task<T> task, CancellationToken token)
{
token.ThrowIfCancellationRequested();
await Task.WhenAny(task, token.WhenCanceled());
token.ThrowIfCancellationRequested();
return await task;
}
public static Task WhenCanceled(this CancellationToken cancellationToken)
{
var tcs = new TaskCompletionSource<bool>();
cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).SetResult(true), tcs);
return tcs.Task;
}
}
因此要使用它,只需将.WaitOrCancel(token)
添加到任何异步调用中即可:
public async Task<Results> ProcessDataAsync(MyData data, CancellationToken token)
{
Client client;
try
{
client = await GetClientAsync().WaitOrCancel(token);
await client.UploadDataAsync(data).WaitOrCancel(token);
await client.CalculateAsync().WaitOrCancel(token);
return await client.GetResultsAsync().WaitOrCancel(token);
}
catch (OperationCanceledException)
{
if (client != null)
await client.CancelAsync();
throw;
}
}
答案 3 :(得分:6)
我只想补充已经接受的答案。我被困在这上面,但我在处理完整的事件时走的是另一条路。我没有运行await,而是在任务中添加了一个完整的处理程序。
Comments.AsAsyncAction().Completed += new AsyncActionCompletedHandler(CommentLoadComplete);
事件处理程序如下所示
private void CommentLoadComplete(IAsyncAction sender, AsyncStatus status )
{
if (status == AsyncStatus.Canceled)
{
return;
}
CommentsItemsControl.ItemsSource = Comments.Result;
CommentScrollViewer.ScrollToVerticalOffset(0);
CommentScrollViewer.Visibility = Visibility.Visible;
CommentProgressRing.Visibility = Visibility.Collapsed;
}
使用这条路线,所有处理都已经完成,当任务被取消时,它只是触发事件处理程序,你可以看到它是否在那里被取消。