0
votes

I have a .Net core app that basically does something like this:

public async Task<IOBoundStuffResult> DoIOBoundStuff()
{
    // Do IO bound stuff ...
    
    return new IOBoundStuffResult
    {
        Id = getIdForThing()
    };
}

public async Task<OtherIOBoundStuffResult> DoOtherIOBoundStuff()
{
    // Do other IO bound stuff ...
    
    return new OtherIOBoundStuffResult
    {
        Uri = getUriForThing()
    };
}

public async Task<IOBoundTaskResult> DoIOBoundStuff()
{
    var ioBoundTask1 = doIOBoundStuff();
    var ioBoundTask2 = doOtherIOBoundStuff();
    
    return await Task.WhenAll(ioBoundTask1, ioBoundTask2)
        .ContinueWith((task) =>
        {
            var id = ioBoundTask1.Result.Id;
            var uri = ioBoundTask2.Result.Uri;
            
            doSomethingWithIdAndUri(id, uri);
            
            return new IOBoundTaskResult
            {
                Id = id,
                Uri = uri
            };
        });
}

public async Task<IActionResult> DoThing()
{
    try
    {
        var cpuBoundTask = Task.Run(() =>
        {
            doCPUBoundStuff();
        });
        
        var ioBoundTask = DoIOBoundStuff();
        
        // do stuff with ioBoundTask, cpuBoundTask
    }
    catch (System.Exception ex)
    {
        // Process System.Exception.AggregateException, other exceptions
    }
}

The problem here is that if something in one of those tasks throws an exception (in particular doSomethingWithIdAndUri()) then the exception is not caught by the try...catch block and results in a crash. I've tried creating continuation tasks with TaskContinuationOptions.OnlyOnFaulted to handle exceptions, but all that seems to do is always cause a TaskCancelledException to be thrown. How can I catch exceptions that are thrown from tasks?

1
Await them? [Adding characters to meet requirements] - Peter Bons
@PeterBons Yes, I am awaiting cpuBoundTask, ioBoundTask, if that's what you mean. - cschadl
I see a Task.Run that doesn't seem to be awaited, is that correct? - Peter Bons

1 Answers

3
votes

In order for a task's exception to "bubble up", it has to be awaited. If, when awaited, an exception has been thrown, that exception will be re-thrown in the current context (though it may sometimes be wrapped in another exception type, such as an AggregateException.

You need to either change, for example, var ioBoundTask = DoIOBoundStuff(); to var ioBoundTask = await DoIOBoundStuff();, or catch the exceptions elsewhere by awaiting the tasks and wrapping the awaits in another try-catch block.

Take care to await every single async method. Avoid using async void as you can't await them. Instead, use async Task.

EDITED to add: Your use of ContinueWith is seems a little peculiar in an async context. Rather than

return await Task.WhenAll(ioBoundTask1, ioBoundTask2)
        .ContinueWith((task) =>
        {
            var id = ioBoundTask1.Result.Id;
            var uri = ioBoundTask2.Result.Uri;
            
            doSomethingWithIdAndUri(id, uri);
            
            return new IOBoundTaskResult
            {
                Id = id,
                Uri = uri
            };
        });

You could write something like

var result1 = await ioBoundTask1;
var result2 = await ioBoundTask2;

var id = result1.Id;
var uri = result2.Uri;
            
doSomethingWithIdAndUri(id, uri);
            
return new IOBoundTaskResult
{
    Id = id,
    Uri = uri
};

This makes it clearer what's happening, and makes it easier to avoid issues like exceptions being thrown in the wrong place / not at all. There are situations when more explicit use of continuations is required, but in general it should be avoided in favour of cleaner, more succinct async/await syntax.

EDIT: Here's a link to my version of the code, where the exception should "bubble up" correctly.