如何在等待中取消任务?

Car*_*rlo 147 c# task-parallel-library .net-4.5

我正在玩这些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()
{
}
Run Code Online (Sandbox Code Playgroud)

Ste*_*ary 216

阅读上取消(这是介绍在.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;
}
Run Code Online (Sandbox Code Playgroud)

  • 只是好奇,是否有一个原因,为什么没有一个例子使用`CancellationToken.IsCancellationRequested`而是建议抛出异常? (9认同)
  • 不是.大多数长时间运行的同步方法都有*某种*方法来取消它们 - 有时通过关闭底层资源或调用另一种方法.`CancellationToken`具有与自定义取消系统互操作所需的所有钩子,但没有任何东西可以取消不可取消的方法. (6认同)
  • 对.我[推荐](http://nitoprograms.blogspot.com/2012/02/async-and-await.html)你从不在`async`方法中使用`Wait`或`Result`; 你应该总是使用`await`来正确地解开异常. (3认同)
  • 哇!这非常有效,现在我需要弄清楚如何在异步方法中处理异常.谢啦!我会读你建议的东西. (2认同)
  • 嘿伙计,如果我无法访问慢速方法,有没有办法做到这一点?例如,假设slowFunc 在一个blackbox 中,您只能访问该方法,而不能修改其中的任何内容? (2认同)
  • @ThyArtIsCode:如果一个方法采用“CancellationToken”,那么预期的行为是在操作被取消时抛出“OperationCanceledException”。这是标准行为。任何其他行为(例如返回哨兵值)都是不寻常的,需要明确的文档。 (2认同)

son*_*que 35

或者,为了避免修改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
Run Code Online (Sandbox Code Playgroud)

您还可以使用https://github.com/StephenCleary/AsyncEx中的良好扩展方法,并使其看起来如下所示:

await Task.WhenAny(task, source.Token.AsTask());
Run Code Online (Sandbox Code Playgroud)

  • 作为整个异步等待实现,它看起来非常棘手。我不认为这样的结构会使源代码更具可读性。 (3认同)
  • 我非常确定这个示例实际上不会以任何方式中断 unmodified-slow-func。它所做的就是与计时器并行启动一个新任务,并等待直到首先完成。如果超时,它确实会停止等待最后的await-task-whenany,但它也**允许运行缓慢的函数继续运行**,这可能会浪费资源,并且与请求中的要求相去甚远。问题。问题是关于**完全停止任务**。我想说这与主题存在严重分歧,请在答案中添加一两句话。 (3认同)

kjb*_*tel 11

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

public async Task<Results> ProcessDataAsync(MyData data)
{
    var client = await GetClientAsync();
    await client.UploadDataAsync(data);
    await client.CalculateAsync();
    return await client.GetResultsAsync();
}
Run Code Online (Sandbox Code Playgroud)

如果你想支持取消,那么最简单的方法是传入一个令牌并检查它是否在每次异步方法调用(或使用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;
    }
}
Run Code Online (Sandbox Code Playgroud)

所以要使用它然后只需添加.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;
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 请注意,虽然这会取消等待任务,但它不会取消实际任务(因此,“UploadDataAsync”可能会在后台继续,但一旦完成,它就不会调用“CalculateAsync”,因为该部分已经停止等待)。这对您来说可能会或可能不会有问题,特别是如果您想重试该操作。如果可能的话,首选将“CancellationToken”一路向下传递。 (2认同)
  • @Miral 确实如此,但是有许多异步方法不接受取消令牌。以 WCF 服务为例,当您使用异步方法生成客户端时,该服务将不包含取消令牌。事实上,正如示例所示,并且正如 Stephen Cleary 也指出的那样,假设长时间运行的同步任务有某种方法可以取消它们。 (2认同)
  • 这就是为什么我说“如果可能的话”。大多数情况下,我只是想提及这个警告,以便以后找到这个答案的人不会得到错误的印象。 (2认同)

Sme*_*egs 6

我只想补充已经接受的答案.我被困在这上面,但我在处理完整的事件时走的是另一条路.我没有运行await,而是为任务添加了一个完整的处理程序.

Comments.AsAsyncAction().Completed += new AsyncActionCompletedHandler(CommentLoadComplete);
Run Code Online (Sandbox Code Playgroud)

事件处理程序看起来像这样

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;
}
Run Code Online (Sandbox Code Playgroud)

使用此路由,所有处理都已为您完成,当任务被取消时,它只会触发事件处理程序,您可以看到它是否在那里被取消.