176

我正在玩这些 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()
{
}
4

4 回答 4

256

阅读Cancellation(它是在 .NET 4.0 中引入的,从那时起基本上没有改变)和基于任务的异步模式,它提供了有关如何使用CancellationToken方法async的指南。

总而言之,您将 a 传递给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;
}
于 2012-04-13T02:43:33.940 回答
45

或者,为了避免修改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());
于 2015-10-09T13:49:23.660 回答
22

一种尚未涉及的情况是如何在异步方法中处理取消。以一个简单的例子为例,您需要将一些数据上传到服务,让它计算一些东西,然后返回一些结果。

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以便它最终可以处理取消。不建议尝试中止线程。

于 2018-11-20T13:01:29.147 回答
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;
}

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

于 2012-08-02T01:22:15.020 回答