Reputation: 1356
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.
Upvotes: 0
Views: 96
Reputation: 73482
AFAIU you can't modify the implementation of the interfaces and yet you'd like to have consistent exception handling strategy.
Well, you can wrap the call in another async method.
private async Task DoSomethingAsync(IMyInterface instance, Context context)
{
await instance.DoSomethingAsync(context);
}
And call it as
foreach (var impl in implementors)
{
var context = ...;
tasks.Add(DoSomethingAsync(impl, context));
}
This way, exception will not be propagated immediately instead wrapped in a Task
and returned by async method.
To make it even more nicer you can create a Decorator and implement the method as async
there. Then decorate all other implementations with your decorator before calling the method.
On the other hand if you can modify the implementation, servy's answer is the way to go.
Upvotes: 1
Reputation: 203835
If you want to return a faulted Task
rather than having the method itself throw an exception, you need to create a Task
, mark it as faulted, and return it.
Using .NET 4.6 you can just write:
return Task.FromException(new Exception("oh the humanity"));
If you're using an earlier version of .NET you can use the following FromException
implementation:
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;
}
Upvotes: 0