I have the following scenario:
Component A provides an interface IMyInterface and a registration mechanism for other components to register their implementors.
The interface looks like:
public interface IMyInterface
{
Task DoSomethingAsync(Context context);
}
In component A I want to start the method on for all implementors "in parallel" and then await the tasks. The implementors may throw exceptions in their implementation (in my concrete scenario, IO is involved and I expect IOExceptions to occur from time to time; component A makes sure to catch the exceptions correctly...).
The code looks like the following
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...
}
So here is my problem:
Since I am not using await for the single tasks, the exception behaviour depends on the way the implementor "creates" the returned 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;
}
}
(note the difference: the first implementor used the async keyword.)
When calling DoSomethingAsync on implementor 1, no exception is thrown in component A. The task object is set to 'faulted' and I can retrieve the exception from the Task. -> as I expected.
When calling DoSomethingAsync on implementor 2, an exception is immediately thrown. -> not what I want.
So here's the question: How can I handle such situations to always observe behaviour 1? I have no control over how the authors of other components implement my interface.