Reputation: 103
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
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 await
s and resumes on a thread pool thread.Main
is doing an await Task.Delay
.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..Wait()
).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
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