c# - .NET 是否在新的不同线程池线程上恢复等待继续,还是重用先前恢复的线程?

标签 c# multithreading asynchronous async-await

.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/

相关文章:

java - 让Fragment等待Thread完成

c# - 在 ASP.NET MVC 2 异步 Controller 中, Action 过滤器是否异步执行?

linux - 如何设计 nanomsg 套接字和 tty 或 Netlink 的异步处理?

C# 多接口(interface)继承不允许具有相同名称的公共(public)访问修饰符

c# - SqlConnection 为 NULL 的可能原因

c# - LINQ 查询中两个日期之间的日期范围

c# - 在多个线程中处理列表时的空引用

c# - SignalR 意外响应代码 : 500

python - 以关键字参数作为线程启动方法

asynchronous - Node.js异步初始化问题