当我试图将工作代码从.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
算法选择下一个要处理的线程的方式导致的 这篇文章对此进行了很好的阐述
我不明白的是为什么在 .Net Framework 上不会发生这种情况,以及我是否可以在 .Net Core 上进行一些设置,使其正常工作。
P.s. 我并不是想避免使用 TPL,我只是想深入了解问题根源。
有什么建议吗?
Thread.Sleep(500)
的简单阻塞调用替换 REST 调用,这个问题会不会也发生?我问这个是为了排除差异是与RestClient
而非ThreadPool
相关的可能性。 - Theodor ZouliasThreadPool
实现发生了变化。我找不到任何相关信息。 - Lukasz Szczygielek