87

I had a problem with catching the exception from Task.Run which was resolved by changing the code as follows. I'd like to know the difference between handling exceptions in these two ways :

In the Outside method I can't catch the exception, but in the Inside method I can.

void Outside()
{
    try
    {
        Task.Run(() =>
        {
            int z = 0;
            int x = 1 / z;
        });
    }
    catch (Exception exception)
    {
        MessageBox.Show("Outside : " + exception.Message);
    }
}

void Inside()
{
    Task.Run(() =>
    {
        try
        {
            int z = 0;
            int x = 1 / z;
        }
        catch (Exception exception)
        {
            MessageBox.Show("Inside : "+exception.Message);
        }
    });
}
Mohammad Chamanpara
  • 2,049
  • 1
  • 15
  • 23

7 Answers7

89

The idea of using Task.Wait will do the trick but will cause the calling thread to (as the code says) wait and therefore block until the task has finalized, which effectively makes the code synchronous instead of async.

Instead use the Task.ContinueWith option to achieve results:

Task.Run(() =>
{
   //do some work
}).ContinueWith((t) =>
{
   if (t.IsFaulted) throw t.Exception;
   if (t.IsCompleted) //optionally do some work);
});

If the task needs to continue on the UI thread, use the TaskScheduler.FromCurrentSynchronizationContext() option as parameter on continue with like so:

).ContinueWith((t) =>
{
    if (t.IsFaulted) throw t.Exception;
    if (t.IsCompleted) //optionally do some work);
}, TaskScheduler.FromCurrentSynchronizationContext());

This code will simply rethrow the aggregate exception from the task level. Off course you can also introduce some other form of exception handling here.

Wheelie
  • 3,866
  • 2
  • 33
  • 39
Menno Jongerius
  • 890
  • 6
  • 3
  • 13
    For Fire and Forgot Tasks I usually use `.ContinueWith(t => ... log every ((AggregateException)t.Exception).Flatten().InnerExceptions ... , TaskContinuationOptions.OnlyOnFaulted)`. – Jürgen Steinblock Aug 09 '18 at 06:29
  • 1
    I don't see the point of running a task on another thread to finally wait for it to complete, your answer keeps the parallel behavior :) – Thomas LAURENT Apr 23 '20 at 18:26
  • 1
    I don't understand "throw t.Exception". What does this do? I tried surrounding the Task.Run with a try/catch but nothing was caught. – Craig Nov 13 '21 at 04:07
  • 1
    @thomas - i think this is used if the data it processes is not going to be displayed to the response or is not needed in other parts of current flow – aj go May 10 '22 at 04:42
65

When a task is run, any exceptions that it throws are retained and re-thrown when something waits for the task's result or for the task to complete.

Task.Run() returns a Task object that you can use to do that, so:

var task = Task.Run(...)

try
{
    task.Wait(); // Rethrows any exception(s).
    ...

For newer versions of C# you can use await instead ot Task.Wait():

try
{
    await Task.Run(...);
    ...

which is much neater.


For completeness, here's a compilable console application that demonstrates the use of await:

using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    class Program
    {
        static void Main()
        {
            test().Wait();
        }

        static async Task test()
        {
            try
            {
                await Task.Run(() => throwsExceptionAfterOneSecond());
            }

            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }
        }

        static void throwsExceptionAfterOneSecond()
        {
            Thread.Sleep(1000); // Sleep is for illustration only. 
            throw new InvalidOperationException("Ooops");
        }
    }
}
Matthew Watson
  • 104,400
  • 10
  • 158
  • 276
  • 6
    Also `task.Result` throws an exception, if any. – VMAtm Aug 18 '15 at 09:37
  • 8
    Even with newer versions of C#, it isn't *always* possible to use `await` instead of `Task.Wait()`. From [await (C# Reference)](https://msdn.microsoft.com/en-us/library/hh156528%28v=vs.140%29.aspx): "The asynchronous method in which **await is** used must be modified by the [async](https://msdn.microsoft.com/en-us/library/hh156513.aspx) keyword." – DavidRR May 16 '16 at 19:33
  • var task = Task.Run(...) and after task.Wait() call sync, of course catched exception as all sync ops. second called never version await Task.Run(...) call async and again you cant catch exception. or I messed up... :) – Nuri YILMAZ Aug 02 '17 at 13:03
  • @NuriYILMAZ I'm guessing you messed up. ;) I've appended a compilable example to illustrate using `await` to catch the exception. Note that the `test.Wait()` in `Main()` is to allow the async method to be called - but it's not catching the the exception- the `await` code is catching the exception.. – Matthew Watson Aug 03 '17 at 08:42
  • Not super related to the subject, but it's a bad practice to use Thread.Sleep in async code, which is what throwsExceptionAfterOneSecond is intended to be, since it's a blocking call. You should use Task.Delay() instead. – Erman Akbay Feb 16 '20 at 07:27
  • 3
    @ErmanAkbay No, in my example `throwsExceptionAfterOneSecond()` is NOT intended to be async. It's supposed to be a synchronous method that takes a while to run and then throws an exception. If it *was* async, then we wouldn't need to use `Task.Run()` to run it in the first place (you would just await it), and the whole example falls to pieces. Remember, this example is intended to show how you get an exception from `Task.Run()`. If you made `throwsExceptionAfterOneSecond()` async, I feel that it would distract from what the code is trying to demonstrate. – Matthew Watson Feb 16 '20 at 09:33
7

In your Outside code you only check whether starting a task does not throw exception not task's body itself. It runs asynchronously and the code which initiated it is done then.

You can use:

void Outside()
{
    try
    {
        Task.Run(() =>
        {
            int z = 0;
            int x = 1 / z;
        }).GetAwaiter().GetResult();
    }
    catch (Exception exception)
    {
        MessageBox.Show("Outside : " + exception.Message);
    }
}

Using .GetAwaiter().GetResult() waits until task ends and passes thrown exception as they are and does not wrap them in AggregateException.

michal.jakubeczy
  • 8,221
  • 1
  • 59
  • 63
6

You can just wait, and then exceptions bubble up to the current synchronization context (see answer by Matthew Watson). Or, as Menno Jongerius mentions, you can ContinueWith to keep the code asynchronous. Note that you can do so only if an exception is thrown by using the OnlyOnFaulted continuation option:

Task.Run(()=> {
    //.... some work....
})
// We could wait now, so we any exceptions are thrown, but that 
// would make the code synchronous. Instead, we continue only if 
// the task fails.
.ContinueWith(t => {
    // This is always true since we ContinueWith OnlyOnFaulted,
    // But we add the condition anyway so resharper doesn't bark.
    if (t.Exception != null)  throw t.Exception;
}, default
     , TaskContinuationOptions.OnlyOnFaulted
     , TaskScheduler.FromCurrentSynchronizationContext());
Diego
  • 18,035
  • 5
  • 62
  • 66
3

When option "Just My Code" is enabled, Visual Studio in some cases will break on the line that throws the exception and display an error message that says:

Exception not handled by user code.

This error is benign. You can press F5 to continue and see the exception-handling behavior that is demonstrated in these examples. To prevent Visual Studio from breaking on the first error, just disable Just My Code checkbox under Tools > Options > Debugging > General.

Nastaran Hakimi
  • 695
  • 1
  • 16
  • 36
Mabito
  • 31
  • 3
  • Bingo, this was it for me. Debugger was stopping on `ThrowIfCancellationRequested()` instead of it being caught in the calling method. – Mike Lowery Nov 14 '20 at 02:46
0

For me I wanted my Task.Run to continue on after an error, letting the UI deal with the error as it has time.

My (weird?) solution is to also have a Form.Timer running. My Task.Run has it's queue (for long-running non-UI stuff), and my Form.Timer has its queue (for UI stuff).

Since this method was already working for me, it was trivial to add error handling: If the task.Run gets an error, it adds the error info to the Form.Timer queue, which displays the error dialog.

-2

Building on @MennoJongerius answer the following keeps asynchronousity and moves the exception from the .wait to the Async Completed event handler:

Public Event AsyncCompleted As AsyncCompletedEventHandler

).ContinueWith(Sub(t)
                   If t.IsFaulted Then
                       Dim evt As AsyncCompletedEventHandler = Me.AsyncCompletedEvent
                       evt?.Invoke(Me, New AsyncCompletedEventArgs(t.Exception, False, Nothing))
                   End If
               End Sub)
David
  • 35
  • 3