1

I have the following code for sample console application, but the method specified in Task continuation using TaskContinuationOptions.OnlyOnFaulted never gets called.

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

namespace Sample
{
    class Program
    {
        public static void Main(string[] args)
        {
            int index = 0;
            var cts = new CancellationTokenSource();

            Task.Factory.StartNew(() => NewTask(index), cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default)
                .ContinueWith(HandleException, cts.Token, TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.Default);

            // Some code
            // ...

            Console.ReadLine();
        }

        private static async Task NewTask(int index)
        {
            Console.WriteLine(index);
            await Wait();
        }

        private static async Task Wait()
        {
            await Task.Delay(500);
            throw new Exception("Testing 123");
        }

        private static void HandleException(Task task)
        {
            if (task != null && task.Exception != null)
            {
                var exceptions = task.Exception.Flatten().InnerExceptions;
                if (exceptions != null)
                    foreach (var exception in exceptions)
                        Console.WriteLine(exception.Message);
            }
        }
    }
}

Here, when an exception is thrown from Wait() method, instead of HandleException(...) being called, either program crashes or debugger shows an unhandled exception dialog box.

EDITED: Return type of NewTask method is Task.

Tejas Vora
  • 538
  • 9
  • 19
  • Change your code like this: `Task.Factory.StartNew(() => NewTask(index), cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default).Unwrap().ContinueWith(HandleException, cts.Token, TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.Default);` Note `Unwrap()`, more details [here](http://blogs.msdn.com/b/pfxteam/archive/2011/10/24/10229468.aspx). – noseratio Apr 28 '14 at 22:18
  • No, don't do this as it only serves to make the calling code more complicated. The asynchronous function should be called directly. – Adam Robinson Apr 29 '14 at 19:51
  • @AdamRobinson, *"The asynchronous function should be called directly"* - **usually so, but it *really* depends on what you're doing inside the `async` function**. One notable case is when you need to use a non-default task scheduler. Another possible case is when there's a piece of CPU-bound or blocking code at the begging of the `async` method. That's why `Task.Run` understands and unwraps `async` lambdas, and that's why `Task.Unwrap` exists. – noseratio Apr 29 '14 at 23:36
  • @Noseratio: Certainly in some cases, but not in the case that the OP has presented. This person is clearly trying to learn about the TPL and `async`/`await`, and trying to explain the corner cases here is likely going to cause more confusion than clarity. – Adam Robinson Apr 30 '14 at 03:12
  • @AdamRobinson, I recon you're right, in which case mixing `async`/`await` and `ContinueWith` is not a good idea at all, as @SephenCleary points out. It's got to be "async all the way down". In case with a console app, the top level handler could do `Task.Wait()` or simply take [some fire-and-forget approach](http://stackoverflow.com/q/22864367/1768303) if it doesn't care about completion. – noseratio Apr 30 '14 at 03:31

2 Answers2

4

By calling Factory.StartNew, you're actually creating a task whose method body just initiates your actual task, but your actual task has no continuation on it; only the outer, superfluous task (which won't throw an exception) has one.

Additionally, your async function is async void. This makes it impossible to add a continuation. I'm going to go one step further and say that you should never, ever, make a function that's async void. It should always be async Task.

If you make that syntax change, your calling syntax is actually simpler:

NewTask(index).ContinueWith(HandleException, cts.Token, 
    TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.Default);

However, bear in mind that cancellation is not something that you get for free; you either need to be passing your token along to other asynchronous tasks that make use of it or you need to be checking it youself (or call .ThrowIfCancellationRequested()).

As an aside, by convention any function which returns a Task or Task<T> should have its name end with Async (i.e. it should be named NewTaskAsync).

Adam Robinson
  • 182,639
  • 35
  • 285
  • 343
  • You are creating two tasks: one is the return value of `Task NewTask(int index)`, the other is the return value of your call to `Task.Factory.StartNew`. The latter is a useless wrapper over the former. You are attaching your continuation to that wrapper, rather than to the actually useful inner task that might throw an exception. – Asik Apr 28 '14 at 22:12
  • Returning void is justified in some cases [Best Practices in Asynchronous Programming](https://msdn.microsoft.com/en-us/magazine/JJ991977.aspx) – Jeff Ward Aug 19 '15 at 16:03
3

I have the following code for sample console application

I assume you're doing this to learn async and await. Here's some tips.

  • Don't use Task.Factory.StartNew. Most of the time you don't need to execute code on a background thread, but if you do, then use Task.Run.
  • Don't use ContinueWith. Use await instead.
  • Follow the Task-based Asynchronous Pattern.
  • Understand that Console applications are not a normal use case for asynchronous applications. If you're planning to end up in a UI application, then you should start in a UI application.

For Console apps, just write a Main like this:

public static void Main(string[] args)
{
  MainAsync().Wait();
}

And then put your real logic into a real async method:

public static async Task MainAsync()
{
  ...
}

You may find my async intro helpful.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • Got it ... Only thing is, my application is a Windows Service and I need to cancel all ongoing running tasks (downloading files, writing files on Disk) when Windows service is stopped, in which, I set the `CancellationToken` to be cancelled. – Tejas Vora Apr 29 '14 at 17:11
  • @user1241991: That's a fine approach. – Stephen Cleary Apr 29 '14 at 17:19
  • @user1241991: The `async` and `await` keywords don't really deal with your ability or inability to cancel an operation. You need to pass a `CancellationToken` to your function(s) and actually proactively check it throughout the execution at whatever points would allow you to actually cancel your operation. – Adam Robinson Apr 29 '14 at 19:50