.NET 是否在新的不同线程池线程上恢复等待继续,还是重用先前恢复的线程?
让我们在下面的 .NET Core 控制台应用程序中的 C# 代码中想象一下:
using System;
using System.Threading;
using System.Threading.Tasks;
namespace NetCoreResume
{
class Program
{
static async Task AsyncThree()
{
await Task.Run(() =>
{
Console.WriteLine($"AsyncThree Task.Run thread id:{Thread.CurrentThread.ManagedThreadId.ToString()}");
});
Console.WriteLine($"AsyncThree continuation thread id:{Thread.CurrentThread.ManagedThreadId.ToString()}");
}
static async Task AsyncTwo()
{
await AsyncThree();
Console.WriteLine($"AsyncTwo continuation thread id:{Thread.CurrentThread.ManagedThreadId.ToString()}");
}
static async Task AsyncOne()
{
await AsyncTwo();
Console.WriteLine($"AsyncOne continuation thread id:{Thread.CurrentThread.ManagedThreadId.ToString()}");
}
static void Main(string[] args)
{
AsyncOne().Wait();
Console.WriteLine("Press any key to end...");
Console.ReadKey();
}
}
}
它会输出:
AsyncThree Task.Run thread id:4
AsyncThree continuation thread id:4
AsyncTwo continuation thread id:4
AsyncOne continuation thread id:4
Press any key to end...
我试图添加
ConfigureAwait(false)
每次等待后 Task
,但它会得到相同的结果。正如我们所见,似乎所有的 await 延续都重用了在
Task.Run
中创建的线程。的 AsyncThree()
方法。我想问一下.NET是否会一直在之前的恢复线程上恢复等待继续,或者在某些情况下它会从线程池中应用一个新的不同线程?
我知道有答案继续将在 上恢复一个线程池线程 在下面的讨论中:
async/await. Where is continuation of awaitable part of method performed?
让我们排除
SynchronizationContext
上面链接中的情况,因为我们现在正在讨论 .NET 控制台应用程序。不过我想问一下好像那个线程池线程在我的例子中总是 thread id 4
,不知道是不是因为thread id 4
在线程池中总是空闲的,所以每次continuation 都是巧合重用它,还是.NET 有机制会尽可能多地重用之前的recovery 线程?每个延续是否有可能在不同的线程池线程上恢复,如下所示?
AsyncThree Task.Run thread id:4
AsyncThree continuation thread id:5
AsyncTwo continuation thread id:6
AsyncOne continuation thread id:7
Press any key to end...
最佳答案
Does .NET resume an await continuation on a new different thread pool thread or reuse the thread from a previous resumption?
两者都不。默认情况下,当
await
ing Task
s, await
will capture a "context" and use that to resume the asynchronous method .这个“上下文”是 SynchronizationContext.Current
, 除非是 null
,在这种情况下,上下文是 TaskScheduler.Current
.在您的示例代码中,上下文是线程池上下文。谜题的另一部分没有记录:
await
uses the TaskContinuationOptions.ExecuteSynchronously
flag .这意味着当 Task.Run
任务完成(由线程 4
),其继续立即同步运行 - if possible .在您的示例代码中,延续可能会同步运行,因为线程 4
上有足够的堆栈并且继续应该在线程池线程和线程 4
上运行是线程池线程。同样,当
AsyncThree
完成,继续 AsyncTwo
立即同步运行 - 再次在线程 4
上运行因为它符合所有标准。这是一种优化,在像 ASP.NET 这样的场景中特别有用,在这些场景中,通常有一个
async
链。方法并完成一项任务(例如,读取数据库)以完成整个链并发送响应。在这些情况下,您希望避免不必要的线程切换。一个有趣的副作用是你最终会得到一个“反向调用堆栈”:线程池线程
4
运行你的代码然后完成 AsyncThree
然后 AsyncTwo
然后 AsyncOne
,并且这些完成中的每一个都在实际调用堆栈上。如果在 WriteLine
上放置断点在 AsyncOne
(并查看外部代码),您可以看到ThreadPoolWorkQueue.Dispatch
在哪里(间接)称为 AsyncThree
其中(间接)称为 AsyncTwo
其中(间接)称为 AsyncOne
.
关于c# - .NET 是否在新的不同线程池线程上恢复等待继续,还是重用先前恢复的线程?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59649998/