Search code examples
c#.netmultithreadingcancellation

C# Throw OperationCanceledException from inside CancellationToken.Register


I have a long running operation that I want to cancel after, say 5 secs. Unfortunately, polling for IsCancellationRequested is not possible (long story).

I used the code below to throw an OperationCanceledException inside the cancellation callback. I wanted to catch the exception in the main thread and handle it so that I can exit the application completely.

This doesn't seem to work properly as this results in an unhandled exception and the application doesn't terminate gracefully.

Any help is appreciated. Thanks!

    void TestTimeOut()
    {
        var cts = new CancellationTokenSource();
        cts.CancelAfter(5000);

        try
        {
            var task = Task.Run(() => LongRunningOperation(cts.Token));
            task.ContinueWith(t => Console.WriteLine("Operation cancelled"), TaskContinuationOptions.OnlyOnFaulted);
            task.Wait();
        }
        catch (AggregateException e)
        {
            //Handle
        }
    }


    void LongRunningOperation(CancellationToken token)
    {
        CancellationTokenRegistration registration = token.Register(
            () =>
            {
                throw new OperationCanceledException(token); 
            });

        using (registration)
        {
            // long running operation here
        }
    }

Solution

  • Your code has many No-No-es, but I guess you just using it as a demo for your problem. The Solution is TaskCompletionSource, My Demo is ugly too, too many layers of Task.Run(), if you use Async, you should async all the way down. So don't use it in PRD, study TaskCompletionSource yourself and figure out a better solution.

        static void LongRunningOperation(CancellationToken token)
        {
            TaskCompletionSource<int> tcs1 = new TaskCompletionSource<int>();
            token.Register(() => { tcs1.TrySetCanceled(token); });
            Task.Run(() =>
            {
                // long running operation here
                Thread.Sleep(10000);
                tcs1.TrySetResult(0);
            }, token);
            tcs1.Task.Wait();
        }