如何取消等待中的任务?

我玩这些 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()
{
}
200572 次浏览

阅读 取消(它是在。NET 4.0和 基于任务的异步模式,它提供了如何使用 CancellationTokenasync方法的指南。

总而言之,您需要向每个支持取消的方法传递一个 CancellationToken,并且该方法必须定期检查它。

private async Task TryTask()
{
CancellationTokenSource source = new CancellationTokenSource();
source.CancelAfter(TimeSpan.FromSeconds(1));
Task<int> task = Task.Run(() => slowFunc(1, 2, source.Token), source.Token);


// (A canceled task will raise an exception when awaited).
await task;
}


private int slowFunc(int a, int b, CancellationToken cancellationToken)
{
string someString = string.Empty;
for (int i = 0; i < 200000; i++)
{
someString += "a";
if (i % 1000 == 0)
cancellationToken.ThrowIfCancellationRequested();
}


return a + b;
}

我只是想补充一下已经被接受的答案。我被这个问题困住了,但是在处理整个事件时,我选择了一条不同的路线。我没有等待,而是向任务添加了一个已完成的处理程序。

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;
}

使用这个路由,所有的处理都已经为您完成,当任务被取消时,它只触发事件处理程序,您可以查看它是否在那里被取消。

或者,为了避免修改 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());

一个没有涉及到的情况是如何在异步方法内部处理取消。举个简单的例子,你需要上传一些数据到一个服务,让它计算一些东西,然后返回一些结果。

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;
}
}

请注意,这不会停止您正在等待的任务,它将继续运行。您需要使用一种不同的机制来停止它,比如示例中的 CancelAsync调用,或者最好将同一个 CancellationToken传递给 Task,以便它最终能够处理取消操作。尝试中止线程 不推荐