2

When catching the Task.WhenAll AggregateException the TaskCancelledException is not available when other tasks are faulted. Running below code I get the following output:

TaskException
Caught in AggregateException
System.ArgumentOutOfRangeException

TaskCanceledException
Caught in TaskCanceledException

TaskException-TaskCanceledException
Caught in AggregateException
System.ArgumentOutOfRangeException

In the last test TaskCancelledException is not thrown, nor is it in the AggregateException.

Is there a means to also have the TaskCanceledException in the AggregateException, or do I always have to check the tasks exception when other tasks have errors?

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Net.Http;

public static class Program
{
    public static void Main()
    {
        var task1 = Task.FromException(new ArgumentOutOfRangeException());
        var task2 = Task.FromCanceled(new CancellationToken(true));
        Test("TaskException", new[] { task1 });
        Test("TaskCanceledException", new[] { task2 });
        Test("TaskException-TaskCanceledException", new Task[] { task1, task2 });

        static async void Test(string title, Task[] tasks)
        {
            Console.WriteLine();
            Console.WriteLine(title);
            var task = Task.WhenAll(tasks);
            try { await task; }            
            catch (TaskCanceledException)
            {
                Console.WriteLine($"Caught in TaskCanceledException");
            }
            catch
            {
                Console.WriteLine($"Caught in AggregateException");
                if (task.Exception is not null)
                {
                    var t = task.Exception.Flatten();
                    foreach (var x in t.InnerExceptions)
                    {
                        Console.WriteLine(x.GetType());
                    }                   
                }
            }
        }        
    }
}
5
  • Instead of httpTest.SendAsync I would suggest to create explicitly a canceled task with either Task.FromException(new OperationCanceledException()) or Task.FromCanceled(new CancellationToken(true)). It's not clear, just by reading the question, what kind of "canceled" the httpTest.SendAsync task is. Commented Aug 22, 2023 at 15:32
  • @TheodorZoulias Task.FromException(new OperationCanceledException()) is handled the same as the other Task.FromException.. , so its part of the AggregateException. But when Im using HttpClient and a timeout occurs as with above its not part of any AggregateException thrown from the continuation Task of WhenAll, rather its thrown directly and is caught in the first try\catch. I want to be able to catch both the OperationCancelled\TaskCancelled PLUS any other exception that would be thrown for the other tasks Commented Aug 23, 2023 at 7:09
  • How about Task.FromCanceled(new CancellationToken(true))? Have you tried that? Commented Aug 23, 2023 at 7:18
  • I've not said anything about the behavior of the tasks. I've just suggested to replace the httpTest.SendAsync in your example with a task created with either Task.FromException or Task.FromCanceled, and has similar behavior with the httpTest.SendAsync. As it stands now, your example is imbalanced. On one hand we see the Task.FromException<int>(new ArgumentOutOfRangeException()), which is straightforward and clear. On the other hand we see the httpTest.SendAsync, which is a mystery. Commented Aug 23, 2023 at 8:17
  • 1
    Ive updated the example now to use TaskFromCanceled, having same behaviour as httpclient timeout. Commented Aug 23, 2023 at 8:47

2 Answers 2

2

The behavior of the Task.WhenAll method is to return a canceled Task in case some of the tasks are canceled, and a faulted Task in case some of the tasks are faulted. In case the tasks array contains both canceled and faulted tasks, the Task.WhenAll ignores the canceled tasks, and returns a faulted Task that contains the exceptions of the faulted tasks.

If you wonder why the Task.WhenAll behaves this way, you could think that a canceled task contains no exception. Its Exception property is null. The TaskCanceledException emerges only when you await a canceled task, and the only information that it conveys is the CancellationToken that caused the cancellation. Any textual information that was present in the original OperationCanceledException, is lost.

If you want to change the behavior of the Task.WhenAll so that it treats the canceled tasks as faulted, one idea is to pass the tasks through a converter that changes their completion status. Something like this:

public static Task CanceledToFaulted(Task task)
{
    ArgumentNullException.ThrowIfNull(task);
    return task.ContinueWith(t =>
    {
        if (t.IsCanceled)
            return Task.FromException(new TaskCanceledException(t));

        // In any other case, propagate the task as is.
        return t;
    }, CancellationToken.None, TaskContinuationOptions.DenyChildAttach |
        TaskContinuationOptions.ExecuteSynchronously,
        TaskScheduler.Default).Unwrap();
}

Usage example:

Task task = Task.WhenAll(tasks.Select(t => CanceledToFaulted(t)));
Sign up to request clarification or add additional context in comments.

Comments

0

You have to catch them independently, or just use catch (Exception) and filter the exceptions by type.

Be sure to throw any Exceptions that aren't handled so they do not end up getting buried.

2 Comments

Im catching independently as above, what I want to achieve is to catch both the httpTimeout plus the ArgumentOutOfRange that are being thrown from the WhenAll. It seems that the timout exception is never part of the AggregateException.
That is correct. An AggregateException is a different type than ArgumentOutOfRange. Just because AggregateException can contain exceptions, it doesn't mean that it will be thrown to contain them. You have to catch AggregateException and Exception. Its very annoying but logically makes sense.

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.