C# 继续执行任务时保留异常<;T>;
我有一个任务: 如果C# 继续执行任务时保留异常<;T>;,c#,.net,task-parallel-library,C#,.net,Task Parallel Library,我有一个任务: 如果someTask出现故障,这将导致resultTask被取消。我希望这是我的错 Task<B> resultTask = StartMyTask().ContinueWith<B>( t => Foo(t.Result)); 如果您明白我的意思,我怀疑原始异常将出现在AggregateException中的AggregateException中-您只需要打开两次,或者调用外部AggregateException任务连续性是独立的。它们可
someTask
出现故障,这将导致resultTask
被取消。我希望这是我的错
Task<B> resultTask = StartMyTask().ContinueWith<B>(
t => Foo(t.Result));
如果您明白我的意思,我怀疑原始异常将出现在
AggregateException
中的AggregateException
中-您只需要打开两次,或者调用外部AggregateException
任务连续性是独立的。它们可以用来构建您想要的,但它们不是专门为该场景设计的
要问的第一个问题是:关系是否可以被视为父/子关系(例如,Foo
将是StartMyTask
的父关系)?如果这是有意义的,那么您可能能够利用从孩子到父母的状态传播
如果将Foo
视为“父对象”而将StartMyTask
视为“子对象”在设计上不起作用,那么就没有其他选择了。对于您所需要的,Continuations有点低级(请记住,它们只是“在其他任务完成时运行此任务”)
听起来你可能想做一些更像“管道”的事情。目前,更适合这种事情
基于任务的管道还没有真正出现。有一个基于任务的管道类,有一个TPL数据流库,但这两个库目前都在开发中。(例如,管道坚持在单独的线程中运行管道的每个阶段,数据流没有自动传播异常甚至完成的机制)
因此,如果您不能使用Rx,那么我将为任务编写自己的“PipelineTransform”扩展方法,并使用显式TCS正确处理所有三种完成情况。这似乎可行,可能与@Stephen Cleary建议的“PipelineTransform”类似
Task resultTask=StartMyTask()。继续(任务=>
{
var tcs=new TaskCompletionSource();
开关(任务状态)
{
案例任务状态。已取消:
setCancelled();
打破
案例任务状态。出现故障:
SetException(task.Exception);
打破
案例任务状态.RANTO完成:
SetResult(Foo(task.Result));
打破
}
返回tcs.Task;
}).Unwrap();
当您检查任务的结果时,总是会得到AggregateException。
如果您的错误处理代码可以与主代码分离,那么您可以使用某种AOP方法,例如PostSharp:
[ErrorHandling]
public void doWord()
{
string result = G(F());
}
其中错误处理是:
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = true, AllowMultiple = false)]
[MulticastAttributeUsage(
MulticastTargets.Method,
Inheritance = MulticastInheritance.Multicast,
AllowMultiple = false)]
public sealed class ErrorHandlingAttribute : OnMethodBoundaryAspect
{
public override void OnException(MethodExecutionArgs args)
{
base.OnException(args);
Exception ex = args.Exception;
AggregateException ae;
if ((ae = ex as AggregateException) !=null)
ex = ae.InnerExceptions[0];
// your error handling logic
}
}
我理解这似乎有些过分,但这只是一个想法。为了使
任务
完全保留原始任务的异常状态,我们可以将dtb答案中的开关大小写更改为
case TaskStatus.Faulted:
tcs.SetException(task.Exception.InnerExceptions);
break;
(请注意,这是“InnerExceptions”,带有“s”。)
这避免了嵌套的AggregateException问题。没错,Flatte就是这样做的。但是VS仍然用新的AggregateException让我恼火。我真的需要在调试器中禁用AggregateException吗?或者还有其他方法吗?好的,您可以对出现故障的任务进行继续测试,并自行执行展开。如果您需要经常这样做,您可以在扩展方法中执行另一个操作并将其包装。如果我将
t=>Foo(t.Result)
替换为t=>{If(t.IsFaulted)throw t.Exception;return Foo(t.Result);}
则每当我的代码遇到异常(“异常未经用户代码处理”)时,VS仍会进入调试器. 我真的很奇怪为什么第三方物流的设计师们决定取消延续而不是指责它…@dtb:但是它在哪里抛出异常?可能不在t.Result
处。如果您不仅指定onononrantocompletion,则在所有情况下都将运行continuation。如果继续是t=>Foo(t.Result)
并且t
出现故障,那么t.Result
将抛出t.Exception
(调试器将中断,除非我在Debug>Exceptions中为aggregateeexception取消选中“User unhandled”)。Rx确实是我的来源,我对任务在任何地方都不如iobservable那么可组合感到非常沮丧。不幸的是,我不能在这个项目中使用Rx。异步CTP将优雅地解决这个问题Async Task DoIt(){return Foo(wait StartSomeTask());}
,但我不能使用它。您能展示一下PipelineTransform
方法的样子吗?您的问题应该理解为:如何跳过将原始异常包装到continuation中的aggregateeexception中?这是不可能的,我相信这是故意的。你必须习惯于处理第三方物流中的聚合异常,它无处不在。实际上AggregateException有很多助手方法,也请看这篇文章和这篇。@Shrike:我不喜欢嵌套的AggregateException。我把问题的措辞改了一点,是吗?是的。这正是我所想的-只需让扩展方法作为Func
参数传递Foo
。
Task<B> resultTask = StartMyTask().ContinueWith<Task<B>>(task =>
{
var tcs = new TaskCompletionSource<B>();
switch (task.Status)
{
case TaskStatus.Canceled:
tcs.SetCanceled();
break;
case TaskStatus.Faulted:
tcs.SetException(task.Exception);
break;
case TaskStatus.RanToCompletion:
tcs.SetResult(Foo(task.Result));
break;
}
return tcs.Task;
}).Unwrap();
[ErrorHandling]
public void doWord()
{
string result = G(F());
}
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = true, AllowMultiple = false)]
[MulticastAttributeUsage(
MulticastTargets.Method,
Inheritance = MulticastInheritance.Multicast,
AllowMultiple = false)]
public sealed class ErrorHandlingAttribute : OnMethodBoundaryAspect
{
public override void OnException(MethodExecutionArgs args)
{
base.OnException(args);
Exception ex = args.Exception;
AggregateException ae;
if ((ae = ex as AggregateException) !=null)
ex = ae.InnerExceptions[0];
// your error handling logic
}
}
case TaskStatus.Faulted:
tcs.SetException(task.Exception.InnerExceptions);
break;