Elegantly handle task cancellation

后端 未结 6 599
花落未央
花落未央 2021-01-03 18:15

When using tasks for large/long running workloads that I need to be able to cancel I often use a template similar to this for the action the task executes:

pu         


        
相关标签:
6条回答
  • 2021-01-03 18:24

    So, what's the problem? Just throw away catch (OperationCanceledException) block, and set proper continuations:

    var cts = new CancellationTokenSource();
    var task = Task.Factory.StartNew(() =>
        {
            var i = 0;
            try
            {
                while (true)
                {
                    Thread.Sleep(1000);
    
                    cts.Token.ThrowIfCancellationRequested();
    
                    i++;
    
                    if (i > 5)
                        throw new InvalidOperationException();
                }
            }
            catch
            {
                Console.WriteLine("i = {0}", i);
                throw;
            }
        }, cts.Token);
    
    task.ContinueWith(t => 
            Console.WriteLine("{0} with {1}: {2}", 
                t.Status, 
                t.Exception.InnerExceptions[0].GetType(), 
                t.Exception.InnerExceptions[0].Message
            ), 
            TaskContinuationOptions.OnlyOnFaulted);
    
    task.ContinueWith(t => 
            Console.WriteLine(t.Status), 
            TaskContinuationOptions.OnlyOnCanceled);
    
    Console.ReadLine();
    
    cts.Cancel();
    
    Console.ReadLine();
    

    TPL distinguishes cancellation and fault. Hence, cancellation (i.e. throwing OperationCancelledException within task body) is not a fault.

    The main point: do not handle exceptions within task body without re-throwing them.

    0 讨论(0)
  • 2021-01-03 18:25

    Here is how you elegantly handle Task cancellation:

    Handling "fire-and-forget" Tasks

    var cts = new CancellationTokenSource( 5000 );  // auto-cancel in 5 sec.
    Task.Run( () => {
        cts.Token.ThrowIfCancellationRequested();
    
        // do background work
    
        cts.Token.ThrowIfCancellationRequested();
    
        // more work
    
    }, cts.Token ).ContinueWith( task => {
        if ( !task.IsCanceled && task.IsFaulted )   // suppress cancel exception
            Logger.Log( task.Exception );           // log others
    } );
    

    Handling await Task completion / cancellation

    var cts = new CancellationTokenSource( 5000 ); // auto-cancel in 5 sec.
    var taskToCancel = Task.Delay( 10000, cts.Token );  
    
    // do work
    
    try { await taskToCancel; }           // await cancellation
    catch ( OperationCanceledException ) {}    // suppress cancel exception, re-throw others
    
    0 讨论(0)
  • 2021-01-03 18:29

    You could do something like this:

    public void DoWork(CancellationToken cancelToken)
    {
        try
        {
            //do work
            cancelToken.ThrowIfCancellationRequested();
            //more work
        }
        catch (OperationCanceledException) when (cancelToken.IsCancellationRequested)
        {
            throw;
        }
        catch (Exception ex)
        {
            Log.Exception(ex);
            throw;
        }
    }
    
    0 讨论(0)
  • 2021-01-03 18:30

    According to this MSDN blog post, you should catch OperationCanceledException, e.g.

    async Task UserSubmitClickAsync(CancellationToken cancellationToken)
    {
       try
       {
          await SendResultAsync(cancellationToken);
       }
       catch (OperationCanceledException) // includes TaskCanceledException
       {
          MessageBox.Show(“Your submission was canceled.”);
       }
    }
    

    If your cancelable method is in between other cancelable operations, you may need to perform clean up when canceled. When doing so, you can use the above catch block, but be sure to rethrow properly:

    async Task SendResultAsync(CancellationToken cancellationToken)
    {
       try
       {
          await httpClient.SendAsync(form, cancellationToken);
       }
       catch (OperationCanceledException)
       {
          // perform your cleanup
          form.Dispose();
    
          // rethrow exception so caller knows you’ve canceled.
          // DON’T “throw ex;” because that stomps on 
          // the Exception.StackTrace property.
          throw; 
       }
    }
    
    0 讨论(0)
  • 2021-01-03 18:35

    I am not entirely sure of what you are trying to achieve here but I think the following pattern might help

    public void DoWork(CancellationToken cancelToken)
    {
        try
        {
            //do work
            cancelToken.ThrowIfCancellationRequested();
            //more work
        }
        catch (OperationCanceledException) {}
        catch (Exception ex)
        {
            Log.Exception(ex);
        }
    }
    

    You might have observed that I have removed the throw statement from here. This will not throw the exception but will simply ignore it.

    Let me know if you intend to do something else.

    There is yet another way which is quite close to what you have exhibited in your code

        catch (Exception ex)
        {
            if (!ex.GetType().Equals(<Type of Exception you don't want to raise>)
            {
                Log.Exception(ex);
    
            }
        }
    
    0 讨论(0)
  • 2021-01-03 18:43

    C# 6.0 has a solution for this..Filtering exception

    int denom;
    
    try
    {
         denom = 0;
        int x = 5 / denom;
    }
    
    // Catch /0 on all days but Saturday
    
    catch (DivideByZeroException xx) when (DateTime.Now.DayOfWeek != DayOfWeek.Saturday)
    {
         Console.WriteLine(xx);
    }
    
    0 讨论(0)
提交回复
热议问题