1

I am using Task.WhenAll to schedule tasks concurrently (more or less of course) and want to rethrow all exceptions rather than the first one what Task.WhenAll would do. So I want to perfor a continuation whenever any child task faulted or has been canceled. That's what NotOnRanToCompletion means, right?

Well this actually works but in case that all Tasks actually have completed, I get a Task Cancelled exception on the Continuation?

var semaphore = new SemaphoreSlim(10);
var tasks = portList.Select(async port =>
{
    try
    {
        await semaphore.WaitAsync();
        await this.AddDeviceAsync(bioprocessDevicesDto, template, port);
    }
    finally
    {
        semaphore.Release();
    }
});
await Task.WhenAll(tasks).ContinueWith(t =>
{
    // this will make all inner exceptions available
    // never null
    throw t.Exception;
}, TaskContinuationOptions.NotOnRanToCompletion).ConfigureAwait(false);

Visual Studio screenshot

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
yBother
  • 648
  • 6
  • 25

2 Answers2

2

That behavior is by-design and documented.

Note that your code is not awaiting the task returned from WhenAll; it's awaiting the continuation from ContinueWith.

ContinueWith is a low-level method with dangerous default behavior. Use await instead:

var tasks = portList.Select(...);
var allTask = Task.WhenAll(tasks);
try { await allTask; }
catch { throw allTask.Exception; }
Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • hmm that's interesting. all my tests worked as expected with the .ContinueWith() approach. on the other hand my tests revealed that (your) classic try{}catch{} approach just behaves the same like withouht try{}catch{} : only the very first exception will be thrown! how do I get all exceptions then? – yBother Apr 24 '20 at 19:00
  • 1
    It works fine for me. Without the `try`/`catch`, the first exception is raised from this code; with the `try`/`catch`, an `AggregateException` is raised from this code. – Stephen Cleary Apr 24 '20 at 19:45
  • 1
    Yes it does work fine. I made a mistake beforel. I will move this try catch logic to an Task Extension Method 'PreserveAllExceptions' that uses allTasks as parameter so I can use it like this: await Task.WhenAll(tasks).PreserveAllExceptions().ConfigureAwait(false); – yBother Apr 27 '20 at 10:52
  • 1
    Hmmm... I really like the idea of that extension method! – Stephen Cleary Apr 27 '20 at 13:28
  • @yBother you might find this interesting: [I want await to throw AggregateException, not just the first Exception](https://stackoverflow.com/questions/18314961/i-want-await-to-throw-aggregateexception-not-just-the-first-exception). – Theodor Zoulias Jun 13 '22 at 09:53
1

When you look at the TaskContinuationOptions for NotOnRanToCompletion, it states:

This option is not valid for multi-task continuations.

I would try changing NotOnRanToCompletion to another option that handles multi-task continuations.

Community
  • 1
  • 1
Brett Wertz
  • 412
  • 4
  • 19