【发布时间】:2021-02-20 16:26:07
【问题描述】:
尝试将工作代码从 .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 合作,我只是想弄清楚这一点。
有什么建议吗?
【问题讨论】:
-
如果您将 REST 调用替换为像
Thread.Sleep(500)这样的简单阻塞调用,是否也会发生这种情况?我要求排除差异与RestClient而不是ThreadPool相关的可能性。 -
“意味着这个过程中存在饥饿”请解释你是如何得出这个结论的。
-
@TheodorZoulias 我用睡眠进行了测试,然后它作为例外执行。但是,使用 rest 客户端或常规 httprequest - 在 .net core 8(num cores)上,theads 开始执行,然后池添加 1 个线程,直到 20,并且只有在请求开始完成之后。我不明白为什么会在这种情况下发生这种情况。没有等待,因此与排队的延续无关。
-
我不太确定这是怎么回事。但是说请求自然需要大约 1 秒。然后我希望启动 8 个线程,然后池每秒左右添加几个新线程,然后请求应该开始完成,因为您的请求是同步的并且它已经在线程池线程上执行 - 它没有'不需要池中的 new 线程来完成请求。当 requset 是异步的并且它需要池中的 new 线程在异步之后执行继续时,就会发生文章中描述的那种饥饿。
-
@NetanelSwartz 你的问题很有趣。
ThreadPool的实现可能在 .NET Framework 和 .NET Core 之间发生了变化。我找不到任何关于它的信息。
标签: c# multithreading threadpool