C# 优雅地处理任务取消
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/12633903/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Elegantly handle task cancellation
提问by Eamon
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:
当我需要能够取消的大型/长时间运行的工作负载使用任务时,我经常使用与此类似的模板来执行任务:
public void DoWork(CancellationToken cancelToken)
{
try
{
//do work
cancelToken.ThrowIfCancellationRequested();
//more work
}
catch (OperationCanceledException)
{
throw;
}
catch (Exception ex)
{
Log.Exception(ex);
throw;
}
}
The OperationCanceledException should not be logged as an error but must not be swallowed if the task is to transition into the cancelled state. Any other exceptions do not need to be dealt with beyond the scope of this method.
OperationCanceledException 不应被记录为错误,但如果任务要转换到取消状态,则不能被吞下。任何其他异常都不需要处理超出此方法的范围。
This always felt a bit clunky, and visual studio by default will break on the throw for OperationCanceledException (Though I have 'break on User-unhandled' turned off now for OperationCanceledException because of my use of this pattern).
这总是感觉有点笨拙,默认情况下,visual studio 会在抛出 OperationCanceledException 时中断(尽管我现在为 OperationCanceledException 关闭了“中断用户未处理”,因为我使用了这种模式)。
Ideally I think I'd like to be able to do something like this:
理想情况下,我想我希望能够做这样的事情:
public void DoWork(CancellationToken cancelToken)
{
try
{
//do work
cancelToken.ThrowIfCancellationRequested();
//more work
}
catch (Exception ex) exclude (OperationCanceledException)
{
Log.Exception(ex);
throw;
}
}
i.e. have some sort of exclusion list applied to the catch but without language support that is not currently possible (@eric-lippert: c# vNext feature :)).
即有某种排除列表应用于捕获但没有当前不可能的语言支持(@eric-lippert:c# vNext 功能:))。
Another way would be through a continuation:
另一种方法是通过延续:
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
}
but I don't really like that as the exception technically could have more than a single inner exception and you don't have as much context while logging the exception as you would in the first example (if I was doing more than just logging it).
但我真的不喜欢那样,因为从技术上讲,异常可能有多个内部异常,并且在记录异常时没有像第一个示例中那样多的上下文(如果我所做的不仅仅是记录它)。
I understand this is a bit of a question of style, but wondering if anyone has any better suggestions?
我知道这有点风格问题,但想知道是否有人有更好的建议?
Do I just have to stick with example 1?
我只需要坚持示例 1 吗?
Eamon
埃蒙
采纳答案by Dennis
So, what's the problem? Just throw away catch (OperationCanceledException)block, and set proper continuations:
所以有什么问题?只需扔掉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 distinguishes cancellation and fault. Hence, cancellation (i.e. throwing OperationCancelledExceptionwithin task body) is not a fault.
TPL 区分取消和故障。因此,取消(即OperationCancelledException在任务主体内抛出)不是错误。
The main point: do nothandle exceptions within task body without re-throwing them.
要点:不要在不重新抛出它们的情况下处理任务主体内的异常。
回答by Murtuza Kabul
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.
您可能已经注意到我从这里删除了 throw 语句。这不会抛出异常,而是会简单地忽略它。
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);
}
}
回答by user5628548
C# 6.0 has a solution for this..Filtering exception
C# 6.0 对此有一个解决方案..过滤异常
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);
}
回答by Casey Anderson
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
回答by Bondolin
According to this MSDN blog post, you should catch OperationCanceledException, e.g.
根据this MSDN blog post,你应该抓住OperationCanceledException,例如
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:
如果您的可取消方法介于其他可取消操作之间,您可能需要在取消时执行清理。这样做时,您可以使用上面的 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;
}
}
回答by Jesper Meyer
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;
}
}

