c# - .NET Framework 和 .NET Core 之间的线程池差异、线程池饥饿

标签 c# multithreading threadpool

在尝试将工作代码从 .Net Framework 4.6.1 传递到 .Net Core 3.1 时,我偶然发现了一个意外行为
这是代码的简化:

static  void Main(string[] args)
{
    for (int i = 0; i < 20; i++)
    {
        ThreadPool.QueueUserWorkItem(o =>
        {
            Console.Write($"In, ");
            RestClient restClient = new RestClient($"http://google.com");
            RestRequest restRequest = new RestRequest();
            var response = restClient.Get(restRequest);

            Console.Write($"Out, ");
        });
    }

    Console.ReadLine();
}
作为多线程工作的结果,控制台上的预期输出是“In”列表,然后是混合的“In”和“Out”,最后是一些“Out”。这在 .Net Framework 上按预期工作。
像这样的东西:
In, In, In, In, In, In, In, In, In, In, In, In, In, In, In, Out, In, Out,
In, Out, In, Out, In, Out, In, Out, Out, Out, Out, Out, Out, Out, Out,
Out, Out, Out, Out, Out, Out, Out,
但是当在 .Net Core 3.1(同一台机器)上运行完全相同的代码时,看起来我们只有在所有“in”线程完成后才返回写“out”行(我用超过 20 个进行了测试)。
In, In, In, In, In, In, In, In, In, In, In, In, In, In, In, In, In, In,
In, In, Out, Out, Out, Out, Out, Out, Out, Out, Out, Out, Out, Out, Out,
Out, Out, Out, Out, Out, Out, Out,
这意味着进程中存在饥饿,如果添加到线程池的工作项数量是无限的(例如取决于 API),则将永远不会处理 HTTP 响应。
我认为这是因为 ThreadPool算法选择下一个线程来处理 this is a nice article on the subject
我不明白的是为什么它不会发生在 .Net Framework 上,以及我是否可以让它在 .Net Core 上以某种方式工作。
附言我不是想避免与 TPL 合作,我只是想深入了解这一点。
有什么建议?

最佳答案

[已编辑] 这是我发现的
.NET Core 和 .NET Framework 的区别在于 HttpWebRequest.GetResponse() 的实现.在 .NET Framework 中,它使用 Thread.SpinWait(1)而在 .NET Core 中,它是 SendRequest().GetAwaiter().GetResult() - 本质上调用异步实现并对其执行 Wait() 。
异步方法调用依赖于 TaskScheduler执行延续。 TaskScheduler 依赖于 ThreadPool。
通常,线程池以 minThreads = # cores 开头。然后它使用一些算法来慢慢地增加线程数,直到达到 maxThreads。
该代码立即将 20 个阻塞作业发布到线程池。继续作业在它们之后排队。线程池缓慢增加线程数以容纳下载作业,然后才添加处理第一个 Continuation 作业的线程。
另一个有趣的转折是,如果您将最小和最大线程数设置为相同的低数并运行代码,则会死锁。那是因为 Continuation 永远不会收到要执行的线程。关于死锁的更多信息 here .
有多种方法可以解决这个问题

  • 避免混契约(Contract)步和异步代码。一路异步(如果可以)
  • 使用 ThreadPool.SetMinThreads从足够数量的线程开始。您至少需要线程数作为预期的并发下载作业数。
  • 在示例代码中,如果您在发布下载作业之间添加甚至 10-50 毫秒的延迟,则继续作业有机会在两者之间进行调度。

  • (这个问题使用了一种叫做 RestClient 的东西,它可能在后台使用了 HttpClient 或 HttpWebRequest。下面的代码使用了 HttpWebRequest)
    private static void Main(string[] args)
    {
        //ThreadPool.SetMinThreads(4, 4);
        //ThreadPool.SetMaxThreads(4, 4);
        for (var i = 0; i < 20; i++)
            ThreadPool.QueueUserWorkItem(o =>
            {
                Console.Write("In, ");
    
                var r = (HttpWebRequest) WebRequest.Create("http://google.com");
                r.GetResponse();
                //Try this in .Net Framework and get the same result in as in .NET Core.
                //That's because in .NET Core r.GetResponse() essentially does r.GetResponseAsync().Wait()
                //r.GetResponseAsync().Wait();  
    
                Console.Write("Out, ");
            });
    
        Console.ReadLine();
    }
    

    关于c# - .NET Framework 和 .NET Core 之间的线程池差异、线程池饥饿,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64739992/

    相关文章:

    c# - 为什么这段代码消耗越来越多的内存?

    c# - 如何动态设置异步页面指令以便异步方法工作

    c# - 在 DataTable 中查找一行

    java - 第二个客户端套接字没有响应

    c++ - C++多线程环境下的内存访问

    c++ - 如何暂停所有线程的恢复?

    python - 在线程内调用 condition.wait() 会导致检索任何 future 以阻塞主线程

    asp.net - 在 IHttpAsyncHandler 中使用 Task 或 async/await

    c# - ASP.NET 动态添加 UserControl 到 PlaceHolder,不触发 Click 事件,只触发 Page_Load

    c# - 线程池 - 可能的线程执行顺序问题