当其中一个异步处于活动循环中时,我正在观察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和悬挂。Call堆栈看起来像:
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#是否保证一旦生成的异步不会阻止其他异步?
发布于 2018-09-21 23:01:30
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中。
https://stackoverflow.com/questions/52450528
复制相似问题