1

I found this code snippet (simplified version provided):

using System;
using System.Threading.Tasks;

namespace TaskTest
{
    class Program
    {
        static void Main(string[] args)
        {
            var task = SendMessage();
            task.Wait();

            if (task.IsFaulted) // Never makes it to this line
            {
                Console.WriteLine("faulted!");
            }
            Console.Read();
        }

        private static async Task SendMessage()
        {
            await Task.Run(() => throw new Exception("something bad happened"));
        }
    }
}

I'm sure this is a bug since task.Wait(); throws and there is no catch block.

Now I'm wondering when you would need to use task.IsFaulted?

David Klempfner
  • 8,700
  • 20
  • 73
  • 153
  • A `Task` that is not awaited will not throw. Consider a long running task running in the background in *parallel* and not asynchronously. You may need to check to see if the task is finished at somepoint, and if it's work isn't finished you might have to check if has faulted at some point, hence `IsFaulted` – DekuDesu Oct 06 '21 at 03:08
  • @DekuDesu "A Task that is not awaited will not throw", but I'm not awaiting this Task and it does throw, on task.Wait(). By "parallel and not asynchronously", did you mean "synchronously" instead of "asynchronously"? – David Klempfner Oct 06 '21 at 03:17
  • 1
    The Task is throwing the exception. Not the task.Wait() – Rowan Smith Oct 06 '21 at 05:53
  • 2
    Task was designed to store the exception, instead of throwing it right away. Fairly ruinous design decision, one they struggled with quite a bit. An early design threw the exception when the task was finalized, that was, erm, unpractical. That stored exception gets thrown when you obtain the result of the task, either through the Result property or by waiting for the task. As you found out. IsFaulted is only interesting if you use continuations with ContinueWith(), the kind of thing you did before async/await was added. – Hans Passant Oct 06 '21 at 11:19

3 Answers3

3

Here is an example where the IsFaulted could be useful. Let's say that you have started two concurrent tasks, the task1 and task2, and after both of them have completed (Task.WhenAll) you want to handle the exception of each task individually. In that case the ex in the catch (Exception ex) is not useful to you, because it contains only one of the possible exceptions, and the exception is dissociated from the originating task. So you could do something like this:

try
{
    await Task.WhenAll(task1, task2);
}
catch when (task1.IsFaulted || task2.IsFaulted)
{
    if (task1.IsFaulted) HandleException("Task 1", task1.Exception.InnerException);
    if (task2.IsFaulted) HandleException("Task 2", task2.Exception.InnerException);
}

When a task IsFaulted, it is guarantied that its Exception property will not be null. This property returns an AggregateException, with an InnerException also practically guarantied to not be null. That's because it is practically impossible to transition a task to a faulted state, without providing at least one Exception object. And the InnerException contains the first of the InnerExceptions that are stored inside an AggregateException.

In this particular example only the failure case in handled. If none of the tasks is faulted, and at least one is canceled, the TaskCanceledException will propagate.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
2

The Task is throwing the exception, not the Task.Wait().

But there is a subtle way that exceptions get bubbled up when using Task.Wait() vs Task.GetAwaiter().GetResult()

You should probably use

task.GetAwaiter().GetResult();

See this question for a good explanation of how the different Syncronous ways to wait work with exceptions.

Task.IsFaulted is useful when using await Task.WhenAny(); or any other time where you want to check the status of a Task without awaiting it, eg from another synchronization context.

I often find myself using Task.IsCompleted | Faulted | Successful to determine what feedback to give a user in a WinForms scenario.

Rowan Smith
  • 1,815
  • 15
  • 29
  • 2
    *"The `Task` is throwing the exception, not the `Task.Wait()`."* <== I am not sure that I agree with that. Could you explain what you mean? – Theodor Zoulias Oct 06 '21 at 10:51
2

When you await a Task asynchronously the program constantly switches between the calling context and the context of the awaited Task.(this is over generalized)

This means in SendMessage(); the program runs everything before the await call with the Main context, runs the awaited call in a Task, which may or may not run on another thread, and switched back to the original context of Main.

Because you awaited the Task within SendMessage(); the Task can properly bubble up errors to the calling context, which in this case is Main which halts the program.

Both .Wait() and await bubble errors back to the calling context.

In your example of you removed the .Wait();, the Task would run parallel (run synchronously in it's own context on another thread) and no errors would be able to bubble back to Main.

Think of it like you are cooking a two course meal. You could cook it asynchronously by constantly walking between two cooking stations and doing tasks at each a little at a time. Alternatively you could have a friend cook the other meal in parallel with you.

When you cook both meals yourself you will know immediately if you've burned your steak. But if you have your friend cook the steak, but he sucks at cooking steak, you won't know he's burned the steak until you check his work(.IsFaulted).

DekuDesu
  • 2,224
  • 1
  • 5
  • 19