Search code examples
c#unobserved-exception

Can "ThrowIfCancellationRequested" not be caught by TaskScheduler_UnobservedTaskException?


I have a very very strange problem, and here's my codes now:

namespace TaskParallelTest
{
    using System.Threading;
    using System.Threading.Tasks;
    using System;
    using System.IO;

    public class Program
    {
        static Program()
        {
            TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
        }
        private static void DoPrint(int id, CancellationToken cToken)
        {
            Thread.Sleep(100);
            if (!cToken.IsCancellationRequested)
            {
                Console.WriteLine("Id is:" + id + ";Current State:" + cToken.IsCancellationRequested);
                cToken.Register(() => Console.WriteLine("Rollback for:" + id));
            }
        }
        static void Main(string[] args)
        {
            CancellationTokenSource cTokenSource = new CancellationTokenSource();

            Task.Run(() =>
            {
                for (int i = 1; i < 6; i++)
                {
                    cTokenSource.Token.ThrowIfCancellationRequested();
                    DoPrint(i, cTokenSource.Token);
                }
            }, cTokenSource.Token);

            Random r = new Random();

            Thread.Sleep(400);
            cTokenSource.Cancel(true);
            Thread.Sleep(10000);
            GC.Collect();
            GC.WaitForPendingFinalizers();
            Console.WriteLine("OK");
            Console.ReadLine();
        }

        private static void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
        {
            File.WriteAllText("C:\\Resume\\Error.txt", e.Exception.StackTrace);
            e.SetObserved();
        }
    }
}

What makes me feel mad is why the event of "UnobservedTaskException" cannot be caught? I used GC.Collect() and Thread.Sleep(), but without any help……?

Sometimes, the "Error.txt" wasn't created, and sometimes, the file created without anything there....?

enter image description here

【Solved——Now acording to the suggestions, here's the answer】

1) Notice that I should remove "Cancellation" and mock an exception here:

static void Main(string[] args)
        {
            CancellationTokenSource cTokenSource = new CancellationTokenSource();

            Task.Run(() =>
            {
                for (int i = 1; i < 6; i++)
                {
                    if (i==5)
                    {
                        throw new Exception("Error occured!");
                    }
                    DoPrint(i, cTokenSource.Token);
                }
            },cTokenSource.Token)
            .ContinueWith
            (
                t =>
                {
                    Console.WriteLine("Error has happened now.");
                    Console.WriteLine(t.IsFaulted);
                },
                TaskContinuationOptions.OnlyOnFaulted
            );

            Thread.Sleep(400);
            //cTokenSource.Cancel();
            //Thread.Sleep(2000);
            GC.Collect();
            GC.WaitForPendingFinalizers();
            //Thread.Sleep(6000);
            Console.WriteLine("OK");
            Console.ReadLine();
        }

2) Then flatten the Exception (because's that an aggregative exception):

 private static void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
        {
            foreach (var item in e.Exception.Flatten().InnerExceptions)
            {
                Console.WriteLine(item.StackTrace);
            }
            e.SetObserved();
        }

Solution

  • If OperationCanceledException is thrown for particular token, and this is the same token you passed when creating the task - it is not treated as unhandled\unobserved exception, because it's just normal, expected cancellation flow. This exception will instead just set task state to Cancelled. That is so in your case too:

    var task = Task.Run(() =>
    {
       for (int i = 1; i < 6; i++)
       {
           // this exception is associated with cTokenSource.Token
           cTokenSource.Token.ThrowIfCancellationRequested();
           DoPrint(i, cTokenSource.Token);
       }
    }, cTokenSource.Token); // and this is the same token you pass when creating a task
    

    If that were not the case (for example, you pass different token when creating a task) - exception will be intercepted by UnobservedTaskException handler.

    Question is: why at all you want this exception to be treated as unobserved? You expected the task can be cancelled, you then cancel it, it's now in Cancelled state. Nothing unobserved\unhandled.