Task的异常行为取决于`async`关键字

时间:2016-04-13 18:49:37

标签: c# asynchronous exception-handling

我有以下情况:

组件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?我无法控制其他组件的作者如何实现我的界面。

2 个答案:

答案 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;
}