.NET框架和.NET内核之间的线程池差异、线程池匮乏 [英] Thread pool differences between .NET Framework and .NET Core, Thread Pool starvation

查看:0
本文介绍了.NET框架和.NET内核之间的线程池差异、线程池匮乏的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在尝试将工作代码从.Net Framework4.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";,最后是一些多线程工作的结果。这在.Net框架上的工作情况与预期一致。 大概是这样的:

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框架上,以及我是否能以某种方式使它在.Net Core上工作。

附注:我并没有试图避免与第三方物流合作,我只是想弄清楚这件事的真相。

有什么建议吗?

推荐答案

[编辑]这是我找到的

.NET Core和.NET框架的区别在于HttpWebRequest.GetResponse()的实现。在.NET框架中,它使用Thread.SpinWait(1),而在.NET Core中,它使用SendRequest().GetAwaiter().GetResult()-本质上调用异步实现并对其执行Wait()。

异步方法调用依赖TaskScheduler执行延续。TaskScheduler依赖于线程池。

正常情况下,线程池以minThads=#cores开头。然后使用某种算法缓慢增加线程数,直到达到最大线程数。

代码立即将20个阻塞作业发送到线程池。后续作业在它们之后排队。线程池缓慢增加线程数以适应下载作业,然后才会添加处理第一个继续作业的线程。

另一个有趣的问题是,如果将最小线程数和最大线程数都设置为相同的较低值并运行代码,则它会死锁。这是因为延续永远不会接收要在其上执行的线程。有关死锁的更多信息here

有多种方法可以解决此问题

  1. 避免混淆同步和异步代码。完全同步(如果可以的话)
  2. 使用ThreadPool.SetMinThreads从足够数量的线程开始。您至少需要将线程数作为预期的并发下载作业数。
  3. 在示例代码中,如果您在发布下载作业之间添加了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();
}

这篇关于.NET框架和.NET内核之间的线程池差异、线程池匮乏的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆