cpt. jazz
cpt. jazz

Reputation: 1356

Exception behaviour for Task depends on `async` keyword

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

Answers (2)

Sriram Sakthivel
Sriram Sakthivel

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

Servy
Servy

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

Related Questions