C# 优雅地处理任务取消
在为需要取消的大型/长时间运行的工作负载使用任务时,我经常使用类似于此的模板来执行任务执行的操作:C# 优雅地处理任务取消,c#,exception,task-parallel-library,C#,Exception,Task Parallel Library,在为需要取消的大型/长时间运行的工作负载使用任务时,我经常使用类似于此的模板来执行任务执行的操作: public void DoWork(CancellationToken cancelToken) { try { //do work cancelToken.ThrowIfCancellationRequested(); //more work } catch (OperationCanceledException)
public void DoWork(CancellationToken cancelToken)
{
try
{
//do work
cancelToken.ThrowIfCancellationRequested();
//more work
}
catch (OperationCanceledException)
{
throw;
}
catch (Exception ex)
{
Log.Exception(ex);
throw;
}
}
OperationCanceledException
不应被记录为错误,但如果任务要转换为取消状态,则不能被忽略。除此方法的范围外,不需要处理任何其他异常
这总是让人感觉有点笨拙,默认情况下,visual studio会在抛出OperationCanceledException
时中断(尽管由于使用了此模式,我现在已将OperationCanceledException
的“用户未处理时中断”关闭)
理想情况下,我认为我希望能够做到以下几点:
public void DoWork(CancellationToken cancelToken)
{
try
{
//do work
cancelToken.ThrowIfCancellationRequested();
//more work
}
catch (Exception ex) exclude (OperationCanceledException)
{
Log.Exception(ex);
throw;
}
}
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;
}
}
i、 e.将某种排除列表应用于捕获,但没有当前不可能的语言支持(@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
}
但我并不喜欢这样,因为从技术上讲,异常可能不止一个内部异常,而且在记录异常时没有第一个示例中那样多的上下文(如果我不仅仅是记录它的话)
我知道这是一个风格的问题,但不知道是否有人有更好的建议
我必须坚持示例1吗?我不完全确定您在这里想要实现什么,但我认为以下模式可能会有所帮助
public void DoWork(CancellationToken cancelToken)
{
try
{
//do work
cancelToken.ThrowIfCancellationRequested();
//more work
}
catch (OperationCanceledException) {}
catch (Exception ex)
{
Log.Exception(ex);
}
}
您可能已经注意到我已经从这里删除了throw语句。这不会抛出异常,但会忽略它
如果你想做别的事,请告诉我
还有另一种方式,它与您在代码中展示的方式非常接近
catch (Exception ex)
{
if (!ex.GetType().Equals(<Type of Exception you don't want to raise>)
{
Log.Exception(ex);
}
}
catch(异常示例)
{
如果(!ex.GetType().Equals(那么,有什么问题吗?只需扔掉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区分取消和故障。因此,取消(即在任务体内抛出operationCanceledException
)不是故障
要点:在不重新抛出异常的情况下,不要处理任务体中的异常。C#6.0有一个解决方案
以下是您如何优雅地处理任务取消:
处理“开火并忘记”任务
处理等待任务完成/取消
根据,您应该捕获操作取消异常
,例如
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 (Exception ex) exclude (OperationCanceledException)
{
Log.Exception(ex);
throw;
}
}
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;
}
}
这:catch(OperationCanceledException){
将任务的状态设置为RanToCompletion
,而不是cancelled
。当这是一个显著的差异时,会有一些用例。我使用continuation方法的唯一问题是我失去了上下文。例如,如果我正在处理一个项目列表,并且我需要记录异常发生时我在集合中的距离rown.我确实在我原来的问题中遗漏了一些东西,我现在已经确认了,这是对异常ex的一次重新调用,以允许任务转换到故障状态。下面有一些更简单的答案。我使用了一种解决方案,其中日志框架对一些异常执行不同的操作。即忽略操作取消异常,展平AggregateException,只记录InvalidOperationException etc的innerException关键字实际上是'when'not'if'。(OP)的语法是:catch(Exception ex)when(!(ex是OperationCanceledException))我更喜欢更严格的检查:catch(OperationCanceledException e)when(e.CancellationToken==cancelToken)
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;
}
}