How to cancel a Task in await?

后端 未结 4 1346
孤独总比滥情好
孤独总比滥情好 2020-11-22 15:20

I\'m playing with these Windows 8 WinRT tasks, and I\'m trying to cancel a task using the method below, and it works to some point. The CancelNotification method DOES get ca

相关标签:
4条回答
  • 2020-11-22 15:29

    One case which hasn't been covered is how to handle cancellation inside of an async method. Take for example a simple case where you need to upload some data to a service get it to calculate something and then return some results.

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

    If you want to support cancellation then the easiest way would be to pass in a token and check if it has been cancelled between each async method call (or using ContinueWith). If they are very long running calls though you could be waiting a while to cancel. I created a little helper method to instead fail as soon as canceled.

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

    So to use it then just add .WaitOrCancel(token) to any async call:

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

    Note that this will not stop the Task you were waiting for and it will continue running. You'll need to use a different mechanism to stop it, such as the CancelAsync call in the example, or better yet pass in the same CancellationToken to the Task so that it can handle the cancellation eventually. Trying to abort the thread isn't recommended.

    0 讨论(0)
  • 2020-11-22 15:33

    Read up on Cancellation (which was introduced in .NET 4.0 and is largely unchanged since then) and the Task-Based Asynchronous Pattern, which provides guidelines on how to use CancellationToken with async methods.

    To summarize, you pass a CancellationToken into each method that supports cancellation, and that method must check it periodically.

    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;
    }
    
    0 讨论(0)
  • 2020-11-22 15:39

    I just want to add to the already accepted answer. I was stuck on this, but I was going a different route on handling the complete event. Rather than running await, I add a completed handler to the task.

    Comments.AsAsyncAction().Completed += new AsyncActionCompletedHandler(CommentLoadComplete);
    

    Where the event handler looks like this

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

    With this route, all the handling is already done for you, when the task is cancelled it just triggers the event handler and you can see if it was cancelled there.

    0 讨论(0)
  • 2020-11-22 15:46

    Or, in order to avoid modifying slowFunc (say you don't have access to the source code for instance):

    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
    

    You can also use nice extension methods from https://github.com/StephenCleary/AsyncEx and have it looks as simple as:

    await Task.WhenAny(task, source.Token.AsTask());
    
    0 讨论(0)
提交回复
热议问题