我有以下情况:
组件A提供了一个接口IMyInterface
和一个注册机制,供其他组件注册其实现者。
界面如下:
public interface IMyInterface
{
Task DoSomethingAsync(Context context);
}
在组件A中,我希望“并行”为所有实现者启动方法,然后等待任务。 实现者可能会在实现中抛出异常(在我的具体方案中,涉及到IO,我希望IOExceptions不时发生;组件A确保正确捕获异常......)。
代码如下所示
var tasks = new List<Task>();
foreach (var impl in implementors)
{
var context = ...;
tasks.Add(impl.DoSomethingAsync(context));
}
// now do something different that takes some time
try
{
await Task.WhenAll(tasks);
}
catch(Exception e)
{
// swallow. we handle the exceptions for each task below.
}
foreach (var task in tasks)
{
if (task.IsFaulted)
// log, recover, etc...
}
所以这是我的问题:
由于我不使用await
执行单个任务,因此异常行为取决于实现者“创建”返回的Task
的方式。
public class Implementor1 : IMyInterface
{
public async Task DoSomethingAsync(Context context)
{
// no awaits used in code here!
throw new Exception("oh the humanity");
}
}
public class Implementor2 : IMyInterface
{
public Task DoSomethingAsync(Context context)
{
throw new Exception("oh the humanity");
return Task.CompletedTask;
}
}
(注意区别:第一个实现者使用async
关键字。)
在实现者1上调用DoSomethingAsync
时,组件A中不会抛出异常。任务对象设置为'faulted',我可以从Task中检索异常。 - &GT;正如我所料。
在实现者2上调用DoSomethingAsync
时,会立即抛出异常。 - &GT; 不我想要的东西。
所以这是问题: 如何处理这种情况以始终观察行为1?我无法控制其他组件的作者如何实现我的界面。
答案 0 :(得分:1)
AFAIU您无法修改接口的实现,但您希望拥有一致的异常处理策略。
好吧,你可以用另一种异步方法包装调用。
private async Task DoSomethingAsync(IMyInterface instance, Context context)
{
await instance.DoSomethingAsync(context);
}
并将其命名为
foreach (var impl in implementors)
{
var context = ...;
tasks.Add(DoSomethingAsync(impl, context));
}
这样,异常不会立即传播,而是包含在Task
中,并由异步方法返回。
为了让它更好,你可以创建一个Decorator并在那里实现async
方法。然后在调用方法之前用装饰器装饰所有其他实现。
另一方面,如果你可以修改实现,那么servy的答案是可行的。
答案 1 :(得分:0)
如果要返回出现故障的Task
而不是让方法本身抛出异常,则需要创建Task
,将其标记为出现故障并将其返回。
使用.NET 4.6,您只需编写:
return Task.FromException(new Exception("oh the humanity"));
如果您使用的是早期版本的.NET,则可以使用以下FromException
实现:
public static Task FromException(Exception e)
{
var tcs = new TaskCompletionSource<bool>();
tcs.SetException(e);
return tcs.Task;
}
public static Task<T> FromException<T>(Exception e)
{
var tcs = new TaskCompletionSource<T>();
tcs.SetException(e);
return tcs.Task;
}