我正在努力了解Task.ContinueWith的工作方式。考虑以下代码:
private async void HandleButtonClick(object sender, EventArgs e)
{
Console.WriteLine($"HandleButtonClick: a {GetTrdLabel()}");
var t1 = Task.Run(() => DoSomethingAsync("First time"));
Console.WriteLine($"HandleButtonClick: b {GetTrdLabel()}");
await t1;
Console.WriteLine($"HandleButtonClick: c {GetTrdLabel()}");
var t2 = t1.ContinueWith(async (t) =>
{
Console.WriteLine($"t3: a {GetTrdLabel()}");
Thread.Sleep(2000);
Console.WriteLine($"t3: b {GetTrdLabel()}");
await DoSomethingAsync("Second time");
Console.WriteLine($"t3: c {GetTrdLabel()}");
});
Console.WriteLine($"HandleButtonClick: d {GetTrdLabel()}");
await t2;
Console.WriteLine($"HandleButtonClick: e {GetTrdLabel()}");
}
private async Task DoSomethingAsync(string label)
{
Console.WriteLine($"DoSomethingElseAsync ({label}): a {GetTrdLabel()}");
Thread.Sleep(2000);
Console.WriteLine($"DoSomethingElseAsync ({label}): b {GetTrdLabel()}");
await Task.Delay(2000);
Console.WriteLine($"DoSomethingElseAsync ({label}): c {GetTrdLabel()}");
}
private string GetTrdLabel() => $"({Thread.CurrentThread.ManagedThreadId})";
输出如下。我的问题是关于突出显示的行:为什么第一个在await
之后(即托管线程ID 3
之后不继续在捕获的上下文中继续,因为我没有使用{{3} }?第二个 按预期进行,即线程ID 4
。
我觉得这与文档中的“ ... 尝试将连续性调回所捕获的原始上下文”(重点是我的)有关,但我不明白为什么 attempt 在第一种情况下失败。
答案 0 :(得分:2)
试图了解Task.ContinueWith的工作原理
最好忽略ContinueWith
并改用await
。但是,如果您想学习low-level, dangerous的做事方式,那么我将负责。请不要在生产中使用。
要注意的第一件事是ContinueWith
始终将工作安排在任务计划程序中。默认情况下,它不使用默认任务计划程序;默认情况下,它使用 current 任务计划程序。假设HandleButtonClick
由您的UI框架直接调用(而不是例如通过任务计划程序进行计划),则当前没有任务计划程序,因此ContinueWith
将使用默认任务计划程序,即线程池任务计划程序。为避免这种复杂的推理,代码应始终始终将TaskScheduler
传递给ContinueWith
。
接下来要注意的是ContinueWith
无法理解async
的代表。就其而言,async
lambda只是给它一个Func<Task>
委托的一种好方法,而ContinueWith
只关心该方法的初始同步部分。
最后要注意的是线程池线程被认为是可互换的。任何async
/ await
代码都是如此。如果它在线程池上下文中运行并执行await
,则它可以继续在任何线程池线程上执行。它可能与在await
之前运行代码的线程不同。
答案 1 :(得分:-2)
如果在Windows应用程序中尝试相同的代码,您将得到不同的结果。 Windows应用程序的UI线程在不同的上下文中运行,并且控件在等待操作后返回到UI线程。控制台应用程序不是这种情况。