0

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.

2 Answers 2

1

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.

Sign up to request clarification or add additional context in comments.

4 Comments

Clever idea, I like this one! I just managed to break this down to just one line: tasks.Add(Task.Run(async () => await impl.DoSomethingAsync(context)));
@cpt.jazz: I don't think Task.Run is appropriate here; you're changing execution context to the thread pool and putting unnecessary pressure on it.
@Stephen: so, how could this be improved when I want to avoid the separate method?
@cpt.jazz: If you don't want to just call it directly (and catch the exception), and you don't want a separate method, then you'd have to use a lambda and call it inline. Unfortunately, I believe lambdas would require a cast to be called, making the syntax very awkward: tasks.Add(((Func<Task>)(async () => await impl.DoSomethingAsync(context)))());
0

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

4 Comments

Yes, but as I said, I am not in control how the authors of other components implement my interface. When they never use await the compiler suggests to remove the async keyword. Since implementations will most likely do IO: what if the author forgets to wrap the IOException in a Task? How can I write robust code to handle such situations?
@cpt.jazz You catch the exception and return a task representing that exception. Obviously you can't change their method, you change yours (if it already returned exactly what you wanted your method wouldn't need to do anything at all).
so you mean something like this: foreach (var impl in implementors) { var context = ...; try { tasks.Add(impl.DoSomethingAsync(context)); } catch(Exception e) { tasks.Add(Task.FromException(e)); } }
@cpt.jazz Sure, if that's what you want to do.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.