如何在 await 中取消任务?

我正在玩这些 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()
{
}
解决办法

请阅读 取消(.NET 4.0 中引入,此后基本保持不变)和 基于任务的异步模式,其中提供了如何将 CancellationTokenasync 方法一起使用的指南。

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

private async Task TryTask()
{
  CancellationTokenSource source = new CancellationTokenSource();
  source.CancelAfter(TimeSpan.FromSeconds(1));
  Task 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;
}
评论(22)

或者,为了避免修改 slowFunc(例如,你无法访问源代码):

var source = new CancellationTokenSource(); //original code
source.Token.Register(CancelNotification); //original code
source.CancelAfter(TimeSpan.FromSeconds(1)); //original code
var completionSource = new TaskCompletionSource(); //New code
source.Token.Register(() => completionSource.TrySetCanceled()); //New code
var task = Task.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());
评论(7)

我只是想对已经被接受的答案做一些补充。 我在这一点上卡住了,但我在处理完成事件时走的是另一条路。 我没有运行 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;
}

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

评论(0)