优雅地处理任务取消

当我需要能够取消大型/长期运行工作负载的任务时,我经常使用与此类似的模板来执行任务:

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

不应将OperationCanceledException记录为错误,但如果任务要转换为已取消状态,则不得吞下。 除了此方法的范围之外,不需要处理任何其他exception。

这总觉得有些笨重,默认情况下,visual studio会在OperationCanceledException中抛出(虽然因为我使用了这种模式,我现在因为OperationCanceledException而关闭了User-unhandled’)。

理想情况下,我认为我希望能够做到这样的事情:

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

即将某种排除列表应用于catch但没有当前不可能的语言支持(@ eric-lippert:c#vNext feature :))。

另一种方式是通过延续:

 public void StartWork() { Task.Factory.StartNew(() => DoWork(cancellationSource.Token), cancellationSource.Token) .ContinueWith(t => Log.Exception(t.Exception.InnerException), TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.ExecuteSynchronously); } public void DoWork(CancellationToken cancelToken) { //do work cancelToken.ThrowIfCancellationRequested(); //more work } 

但我真的不喜欢这样,因为exception在技术上可能只有一个内部exception而且你在记录exception时没有像第一个例子那样多的上下文(如果我做的不仅仅是记录它)。

我理解这是一个风格问题,但想知道是否有人有更好的建议?

我只需坚持示例1吗?

埃蒙

所以有什么问题? 只需抛弃catch (OperationCanceledException)块,并设置适当的延续:

 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区分取消和错误。 因此,取消(即在任务主体内抛出OperationCancelledException不是错误

要点: 不要在没有重新抛出它们的情况下处理任务体内的exception。

以下是优雅处理任务取消的方法:

处理“即发即忘”的任务

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

处理等待任务完成/取消

 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 

C#6.0有一个解决方案.. 过滤exception

 int denom; try { denom = 0; int x = 5 / denom; } // Catch /0 on all days but Saturday catch (DivideByZeroException xx) if (DateTime.Now.DayOfWeek != DayOfWeek.Saturday) { Console.WriteLine(xx); } 

根据这篇MSDN博客文章 ,您应该捕获OperationCanceledException ,例如

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

如果您的可取消方法介于其他可取消操作之间,则可能需要在取消时执行清理。 这样做时,您可以使用上面的catch块,但一定要正确地重新抛出:

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

我不完全确定你要在这里实现什么,但我认为以下模式可能有所帮助

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

您可能已经观察到我已从此处删除了throw语句。 这不会抛出exception,但会忽略它。

如果您打算做其他事,请告诉我。

还有另一种方法与您在代码中展示的内容非常接近

  catch (Exception ex) { if (!ex.GetType().Equals() { Log.Exception(ex); } }