Kidservice
Kidservice

Reputation: 103

CancellationToken blocks thread execution

I'm trying to use cancellation tokens to break out of a loop from another thread:

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

namespace CancellationTokenTest
{
    class Program
    {
        private static CancellationTokenSource token;

        static async Task Main(string[] args)
        {
            token = new CancellationTokenSource();
            var t1 = LongProcess1();

            for (int i = 0; i < 5; i++)
            {
                try
                {
                    await Task.Delay(2000, token.Token);
                    Console.WriteLine($"Main awaited {i}");
                }
                catch (OperationCanceledException)
                {
                    break;
                }
            }

            Console.WriteLine("Main loop completed");
            t1.Wait();
            Console.WriteLine("Application end");
        }

        static async Task LongProcess1()
        {
            for (int i = 0; i < 5; i++)
            {
                await Task.Delay(1000);
                Console.WriteLine($"LongProcess1 awaited {i}");
            }

            Console.WriteLine("Submitting cancel");
            token.Cancel();     // <------ Blocking execution
            Console.WriteLine("Cancellation done!");
        }
    }
}

The problem is that the line Console.WriteLine("Cancellation done!"); never gets executed, and the Main() method keeps waiting for t1 to complete.

I do not understand why!

I ran this on .NET 5, but it doesn't seem to be framework dependent. Can someone help me understand what's going on?

Upvotes: 2

Views: 1590

Answers (2)

Stephen Cleary
Stephen Cleary

Reputation: 456322

This deadlock is almost exactly like the one described on my blog. One thing you need to be aware of is that CancellationTokenSource.Cancel in this case is running the cancellation token continuations on the thread that calls Cancel.

So, here's what's happening:

  • Main starts LongProcess.
  • LongProcess uses some awaits and resumes on a thread pool thread.
  • In the meantime, Main is doing an await Task.Delay.
  • When LongProcess does the Cancel, it actually resumes executing the Main method on the current thread. You can verify this by placing a breakpoint in your catch block and looking at the call stack.
  • The current thread (a thread pool thread) then blocks on the task (.Wait()).
  • Now, LongProcess cannot complete because the thread currently running it is blocked waiting for it to complete.

To fix it, change the Wait() to an await.

Upvotes: 7

Marc Gravell
Marc Gravell

Reputation: 1062512

The problem is the t1.Wait(); (Wait() is almost always the problem ;p) - this is stealing the thread. Use await t1; instead, and everything will work. Effectively, the cancellation needs to invoke some callbacks (the continuations in the cancellation state) and then return to whatever did the cancellation, and you've inserted a blockage into the cancellation.

Upvotes: 1

Related Questions