CancellationTokenSource.Cancel() 挂起
CancellationTokenSource.Cancel() hangs
我正在观察 CancellationTokenSource.Cancel
当其中一个异步处于活动循环中时挂起。
完整代码:
static async Task doStuff(CancellationToken token)
{
try
{
// await Task.Yield();
await Task.Delay(-1, token);
}
catch (TaskCanceledException)
{
}
while (true) ;
}
static void Main(string[] args)
{
var main = Task.Run(() =>
{
using (var csource = new CancellationTokenSource())
{
var task = doStuff(csource.Token);
Console.WriteLine("Spawned");
csource.Cancel();
Console.WriteLine("Cancelled");
}
});
main.GetAwaiter().GetResult();
}
打印 Spawned
并挂起。调用堆栈看起来像:
ConsoleApp9.exe!ConsoleApp9.Program.doStuff(System.Threading.CancellationToken token) Line 23 C#
[Resuming Async Method]
[External Code]
ConsoleApp9.exe!ConsoleApp9.Program.Main.AnonymousMethod__1_0() Line 34 C#
[External Code]
取消提交 await Task.Yield
将导致输出 Spawned\nCancelled
。
知道为什么吗? C# 是否保证一次产生的异步永远不会阻塞其他异步?
CancellationTokenSource
没有任何任务调度程序的概念。如果回调未在自定义同步上下文中注册,CancellationTokenSource 将在与 .Cancel()
相同的调用堆栈中执行它。在您的情况下,取消回调完成 Task.Delay
返回的任务,然后内联继续,导致 CancellationTokenSource.Cancel
.
内的无限循环
您使用 Task.Yield
的示例仅在竞争条件下才有效。当令牌被取消时,线程还没有开始执行Task.Delay
,因此没有继续内联。如果您更改 Main
以添加暂停,您会发现即使 Task.Yield
:
它仍然会冻结
static void Main(string[] args)
{
var main = Task.Run(() =>
{
using (var csource = new CancellationTokenSource())
{
var task = doStuff(csource.Token);
Console.WriteLine("Spawned");
Thread.Sleep(1000); // Give enough time to reach Task.Delay
csource.Cancel();
Console.WriteLine("Cancelled");
}
});
main.GetAwaiter().GetResult();
}
现在,保护对 CancellationTokenSource.Cancel
的调用的唯一可靠方法是将其包装在 Task.Run
中。
我正在观察 CancellationTokenSource.Cancel
当其中一个异步处于活动循环中时挂起。
完整代码:
static async Task doStuff(CancellationToken token)
{
try
{
// await Task.Yield();
await Task.Delay(-1, token);
}
catch (TaskCanceledException)
{
}
while (true) ;
}
static void Main(string[] args)
{
var main = Task.Run(() =>
{
using (var csource = new CancellationTokenSource())
{
var task = doStuff(csource.Token);
Console.WriteLine("Spawned");
csource.Cancel();
Console.WriteLine("Cancelled");
}
});
main.GetAwaiter().GetResult();
}
打印 Spawned
并挂起。调用堆栈看起来像:
ConsoleApp9.exe!ConsoleApp9.Program.doStuff(System.Threading.CancellationToken token) Line 23 C#
[Resuming Async Method]
[External Code]
ConsoleApp9.exe!ConsoleApp9.Program.Main.AnonymousMethod__1_0() Line 34 C#
[External Code]
取消提交 await Task.Yield
将导致输出 Spawned\nCancelled
。
知道为什么吗? C# 是否保证一次产生的异步永远不会阻塞其他异步?
CancellationTokenSource
没有任何任务调度程序的概念。如果回调未在自定义同步上下文中注册,CancellationTokenSource 将在与 .Cancel()
相同的调用堆栈中执行它。在您的情况下,取消回调完成 Task.Delay
返回的任务,然后内联继续,导致 CancellationTokenSource.Cancel
.
您使用 Task.Yield
的示例仅在竞争条件下才有效。当令牌被取消时,线程还没有开始执行Task.Delay
,因此没有继续内联。如果您更改 Main
以添加暂停,您会发现即使 Task.Yield
:
static void Main(string[] args)
{
var main = Task.Run(() =>
{
using (var csource = new CancellationTokenSource())
{
var task = doStuff(csource.Token);
Console.WriteLine("Spawned");
Thread.Sleep(1000); // Give enough time to reach Task.Delay
csource.Cancel();
Console.WriteLine("Cancelled");
}
});
main.GetAwaiter().GetResult();
}
现在,保护对 CancellationTokenSource.Cancel
的调用的唯一可靠方法是将其包装在 Task.Run
中。