当我需要能够取消的大型/长期运行工作负载的任务时,我经常使用与此类似的模板来执行任务:
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而关闭了'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
}
但我真的不喜欢它,因为异常在技术上可能有多个内部异常,并且在记录异常时没有像第一个示例中那样多的上下文(如果我做的不仅仅是只记录它。)
我理解这是一个风格问题,但想知道是否有人有更好的建议?
我是否必须坚持使用示例1?
埃蒙
答案 0 :(得分:16)
那么,问题是什么?只要扔掉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
)不是错误。
要点:不处理任务正文中的异常而不重新抛出异常。
答案 1 :(得分:9)
以下是优雅处理任务取消的方法:
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
答案 2 :(得分:6)
C#6.0有一个解决方案.. 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);
}
答案 3 :(得分:1)
根据this MSDN blog post,你应该抓住 String className = "index.Abaddon";
Class<?> clazz = Class.forName(className);
Field field = clazz.getDeclaredField("counterList");
if (field.getType().isArray()) {
final String[] arr = (String[]) field.get(clazz.newInstance());
System.out.println(Arrays.toString(arr));
}
,例如
OperationCanceledException
如果您的可取消方法介于其他可取消操作之间,则可能需要在取消时执行清理。这样做时,您可以使用上面的catch块,但一定要正确地重新抛出:
async Task UserSubmitClickAsync(CancellationToken cancellationToken) { try { await SendResultAsync(cancellationToken); } catch (OperationCanceledException) // includes TaskCanceledException { MessageBox.Show(“Your submission was canceled.”); } }
答案 4 :(得分:0)
忽略取消期间的重新抛出实际上很好。如果要避免使用Task.Run / Task.Factory.StartNew模式(您可能应该这样做),则可以执行以下操作:
public void DoWork(CancellationToken cancelToken)
{
try
{
//do work
cancelToken.ThrowIfCancellationRequested();
//more work
}
catch (OperationCanceledException) when (cancelToken.IsCancellationRequested)
{
// ignored
}
catch (Exception ex)
{
Log.Exception(ex);
throw;
}
}
答案 5 :(得分:-2)
我不完全确定你在这里想要实现的目标,但我认为以下模式可能会有所帮助
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);
}
}