Sleiman Jneidi
Sleiman Jneidi

Reputation: 23329

Task and exception silence

Why exceptions thrown within a task are silent exception and you never know if a certain exception has been thrown

try
{

 Task task = new Task(
  () => {
          throw null;
        }
        );
  task.Start();
 }
 catch
 {
  Console.WriteLine("Exception");
 }  

the program run successfully in a complete silence! where the behavior of threads is different

try
{

 Thread thread = new Thread(
  () => {
          throw null;
        }
        );
  thread .Start();
 }
 catch
 {
  Console.WriteLine("Exception");
 }

a null pointer exception will be thrown in this case. What is the difference?

Upvotes: 18

Views: 6159

Answers (4)

Marc Gravell
Marc Gravell

Reputation: 1062865

The behaviour of that scenario depends on what framework you have; in 4.0, you actually need to be careful - if you don't handle TaskScheduler.UnobservedTaskException, it will error later when it gets collected/finalized, and will kill your process.

TaskScheduler.UnobservedTaskException += (sender, args) =>
{
    Trace.WriteLine(args.Exception.Message); // somebody forgot to check!
    args.SetObserved();
};

This changes in 4.5, IIRC.

To check the outcome of a Task that might fail, you could register a continuation - i.e. call ContinueWith and check the result's exception. Alternatively, accessing the .Result of the task (which would also do an implicit Wait()) will re-surface the exception that happened. It is good to observe the result of a task, as that clears the finalization flag, meaning it can be collected more cheaply.

Upvotes: 17

Christian.K
Christian.K

Reputation: 49260

The following assumes .NET 4.0, and the using the default TaskScheduler.

First of all notice that the exceptions are raised inside the delegates you pass, so they are raised in a different thread, not in the one you (logically) are doing your catch. So somehow the exception must be propagated from the thread that executes your delegate / lambda code to the one that started the thread/task.

Note that for Task, I think that the library can also choose not to run it on it's own thread, but rather on the calling thread (but I'm not sure if that was only true for Parallel.ForEach, etc. and not for a "naked" Task object).

For that two happen, two things must be fulfilled:

  1. The calling thread is still active. Otherwise there is nothing left that could actually perform the catch.
  2. The exception that was caused in the thread/task must somehow be preserved and reraised for you to catch it.

Having that said, both of your examples don't wait for the thread/task to finish. In the first example you're missing a task.Wait() (or similar) and in the second a thread.Join(). Depending on your test codes timing behavior this may mean that you may never be able to observe the exception from the thread/task (item 1 above).

Even if you add the two calls this is what happens for me (again .NET 4.0):

  • Task example: the call to task.Wait() actually reraises the exception originally left unhandled in the task's delegate (this is what the TPL will do for you internally), it does wrap it inside a System.AggregateException, which you could/would see if you'd use something more precise then a flat "catch-all".

  • Thread example: the exception raised by the delegate remains unhandled and your application exits (unless you do anything to deal with unhandled exceptions differently)

In other words I would have the examples as follows:

// Thread example
var thread = new Thread(() => { throw null; });
thread.Start();
thread.Join();
// Should never reach here, depending on timing sometimes not even
// until the Join() call. The process terminates as soon as the other
// thread runs the "throw null" code - which can logically happen somewhere
// after the "start" of the "Start()" call.

// Task example
try
{

    var task = new Task(() => { throw null; });
    task.Start();
    task.Wait();
}
catch (AggregateException ex)
{
    Console.WriteLine("Exception: " + ex);
}

Upvotes: 4

Haris Hasan
Haris Hasan

Reputation: 30097

I think you are not getting exception in case of Task because you are not waiting for exception in the main thread. You are just continuing. Put task.Wait() and you will get the exception in main thread.

Upvotes: 1

Rich O'Kelly
Rich O'Kelly

Reputation: 41757

No, tasks are not threads. Tasks represent a high level abstraction - they are a unit of work that is inherently parallelisable. Threads run units of work.

In your first example, you create a unit of work and then tell it to run itself (how it does so is an implementation detail of Task). Whereas in your second example you explicitly schedule a unit of work (it would appear in a different manner to the implementation of Task).

Upvotes: 5

Related Questions