我有一个任务< T>:
Task<A> someTask = ...
此任务可能导致成功,出现故障或取消。
我希望在任务成功时转换结果,如果没有,则保留结果。
当someTask
抛出异常时,这似乎非常困难。
我尝试了什么:
Task<B> resultTask = StartMyTask().ContinueWith<B>(
t => Foo(t.Result),
TaskContinuationOptions.OnlyOnRanToCompletion);
如果resultTask
出现错误,则会导致someTask
被取消。我希望它有问题。
Task<B> resultTask = StartMyTask().ContinueWith<B>(
t => Foo(t.Result));
这会破坏Visual Studio调试器,因为.Result
会引发异常。如果按F5,resultTask
会出现预期的错误,但它会闻起来。
如果resultTask
出现问题,有没有办法让someTask
与someTask
获得相同的结果?
基本上我要做的就是用任务来表达这样的事情:
int F()
{
throw new SomeException();
}
string G(int x)
{
return x.ToString();
}
try
{
string result = G(F());
}
catch (SomeException e)
{
...
}
答案 0 :(得分:5)
我怀疑原始异常会出现在AggregateException
AggregateException
中,如果您明白我的意思 - 您只需要打开两次,或者在外部AggregateException.Flatten()
上打电话AggregateException
1}}。
答案 1 :(得分:4)
任务延续是独立的。它们可用于构建您想要的内容,但它们并非专门针对该场景而设计。
要问的第一个问题是:关系可以被视为父/子关系(例如,Foo
是StartMyTask
的父母)吗?如果这是有道理的,那么你可以利用从孩子到父母的国家传播。
如果将Foo
视为“父级”并将StartMyTask
视为“子级”,则在设计方面不起作用,那么其他选项也很少。对于你需要的东西,延续有点低级别(记住,他们只是“在其他任务完成时运行此任务”)。
听起来你可能正试图做一些更像“管道”的事情。目前,Rx更适合此类事情。
基于任务的管道还没有真正到来。 ParallelExtensionsExtras library有一个基于任务的管道类,Async CTP有一个TPL数据流库,但目前这两个都没有得到开发。 (例如,Pipeline坚持在一个单独的线程中运行管道的每个阶段,而Dataflow没有自动传播异常甚至完成的机制。)
因此,如果您不能使用Rx,那么我会为Task编写自己的“PipelineTransform”扩展方法,并使用显式TCS正确处理所有三种完成情况。
答案 2 :(得分:4)
这似乎有效,可能类似于 @Stephen Cleary 建议的“PipelineTransform”。
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();
答案 3 :(得分:2)
为了使Task<B>
完全保留原始任务的异常状态,我们可以在dtb的答案中更改开关案例
case TaskStatus.Faulted:
tcs.SetException(task.Exception.InnerExceptions);
break;
(请注意,这是“InnerExceptions”,带有's'。)
这可以避免嵌套的AggregateException问题。
答案 4 :(得分:0)
检查Task的结果时,总是会收到AggregateException。 如果您的错误处理代码可以与主代码分开,那么您可以使用AOP方法,PostSharp用于实例:
[ErrorHandling]
public void doWord()
{
string result = G(F());
}
其中ErrorHandling是:
[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
}
}
我知道这可能看起来有些过分,但这只是一个想法。