如何解决.NET Framework和.NET Core之间的线程池差异,线程池不足
当尝试将工作代码从.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,Out,
但是,当在.Net Core 3.1(同一台机器)上运行完全相同的代码时,似乎我们只有在所有“入”线程完成之后才回写“出”行(我对此进行了很多测试, 20)。
In,
这意味着该进程很饿,如果线程池中添加的工作项的数量是无限的(例如,取决于API),则将永远不会处理HTTP响应。
我认为发生这种情况的原因是ThreadPool
算法选择下一个线程来处理this is a nice article on the subject
我不明白的是为什么它不会在.Net Framework上发生,并且如果我能使其在.Net Core上以某种方式起作用。
P.s。我并不是想避免与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的更多信息。
有多种解决方法
- 避免混合使用同步代码和异步代码。一直保持异步状态(如果可以的话)
- 使用
ThreadPool.SetMinThreads
从足够数量的线程开始。您至少需要线程数量作为预期的并发下载作业数量。 - 在示例代码中,如果您在发布下载作业之间添加了10到50ms的延迟,则继续作业将有机会在其间进行调度。
(问题使用了名为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();
}
,
问题似乎在于ThreadPool是“被设计为最大化吞吐量,而不是最小化延迟”。
我认为以下文章为理解这种行为提供了一个很好的起点: Scastie
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。