3

I'm following the example code here to learn about asynchronous tasks. I've modified the code to write some output of the task's work vs. the main work. The output will look like this:

enter image description here

I noticed that if I remove the Wait() call, the program runs the same except I can't catch the exception that's thrown when the task is canceled. Can someone explain what's going on behind the scenes requiring the Wait() in order to hit the catch block?

One warning, the Visual Studio debugger will erroneously stop on the Console.WriteLine(" - task work"); line with the message "OperationCanceledException was unhandled by user code". When that happens, just click Continue or hit F5 to see the rest of the program run. See http://blogs.msdn.com/b/pfxteam/archive/2010/01/11/9946736.aspx for details.

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

namespace ConsoleApplication1
class Program
{
  static void Main()
  {
     var tokenSource = new CancellationTokenSource();
     var cancellationToken = tokenSource.Token;

    // Delegate representing work that the task will do.
     var workDelegate 
            = (Action)
              (
                () =>
                   {
                       while (true)
                       {
                          cancellationToken.ThrowIfCancellationRequested(); 
              // "If task has been cancelled, throw exception to return"

          // Simulate task work
                  Console.WriteLine(" - task work"); //Visual Studio  
           //erroneously stops on exception here. Just continue (F5). 
           //See http://blogs.msdn.com/b/pfxteam/archive/2010/01/11/9946736.aspx
                          Thread.Sleep(100);
                       }
                   }
              );


     try
     {
     // Start the task
         var task = Task.Factory.StartNew(workDelegate, cancellationToken);

      // Simulate main work
         for (var i = 0; i < 5; i++)
         {
             Console.WriteLine("main work");
             Thread.Sleep(200);
         }

       // Cancel the task
         tokenSource.Cancel();

       // Why is this Wait() necessary to catch the exception?
       // If I reomve it, the catch (below) is never hit, 
       //but the program runs as before.
          task.Wait();
     }
     catch (AggregateException e)
     {
         Console.WriteLine(e.Message);
         foreach (var innerException in e.InnerExceptions)
         Console.WriteLine(innerException.Message);
     }

     Console.WriteLine("Press any key to exit...");
     Console.ReadKey();
   }
}
Vimes
  • 10,577
  • 17
  • 66
  • 86

3 Answers3

7

When the exception is thrown by ThrowIfCancellationRequested, it propagates out of your Task delegate. At that point, it is captured by the framework and added to the list of exceptions for that Task. At the same time, that Task is transitioned to the Faulted state.

Ideally, you want to observe all your Task exceptions. If you're using Tasks as part of the task-based asynchronous pattern, then at some point you should await the Task, which propagates the first exception on the Task. If you're using Tasks as part of the task parallel library, then at some point you should call Wait or Task<T>.Result, which propagates all the exceptions on the Task, wrapped in an AggregateException.

If you do not observe a Task exception, then when the Task is finalized, the runtime will raise TaskScheduler.UnobservedTaskException and then ignore the exception. (This is the .NET 4.5 behavior; the pre-4.5 behavior would raise UnobservedTaskException and then terminate the process).

In your case, you don't wait for the task to complete, so you exit the try/catch block. Some time later, UnobservedTaskException is raised and then the exception is ignored.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • I see two answers saying that the program gets past the catch block before the exception is thrown. Is that different than the ignored `TaskScheduler.UnobservedTaskException` you mention here, or are both occurring? – Vimes Apr 26 '13 at 16:21
  • 1
    Both. Since you're not waiting for the task to complete, you exit the `try`/`catch` block. Some time later, `UnobservedTaskException` is raised and then the exception is ignored. – Stephen Cleary Apr 26 '13 at 17:08
  • This is great information. Did you learn it from the API docs and MSDN articles? I've read a handful, but didn't get a clear view of things like Tasks being associated with a list of exceptions, or the UnobservedTaskException. Or perhaps there's a good book. Thanks. – Vimes Apr 26 '13 at 17:23
  • 1
    Mostly from Stephen Toub's articles on the [Parallel Team blog](http://blogs.msdn.com/b/pfxteam/). – Stephen Cleary Apr 26 '13 at 17:47
1

If you don't wait for the task to finish, your program will continue running and thus you will have passed the catch-block.

If on the other hand you would await the task, the exception would still be caught

Kenneth
  • 28,294
  • 6
  • 61
  • 84
1

If you do not wait for task to complete, your program will exit (or at least leave that area of execution) before the exception is thrown.

If the main thread is already at

Console.ReadKey();

it will not "back up" to the try/catch block.

Eric J.
  • 147,927
  • 63
  • 340
  • 553
  • That would explain it. Does the example behavior come down to how the task is time sliced into the main program, where it could conceivably be caught if the time slices were scheduled "just so"? Or, is `Wait()` the return path for the exception and there would be no way for it to propagate otherwise? (Or, are async exceptions handled independently of the call stack?) Thanks. – Vimes Apr 26 '13 at 16:40