Task.Run WaitAll 正在阻塞

Task.Run with a WaitAll is blocking

我来这里是因为我在使用这段代码时有一个奇怪的行为: 但在那之前,我知道这样做是一个非常糟糕的做法,所以它甚至没有在现实中使用我只是想了解幕后发生的事情,但我的知识真的很差。

这是有问题的代码:

int worker = 0;
int io = 0;
Console.WriteLine($"Worker thread {worker} Io thread {io}");
ThreadPool.GetAvailableThreads(out worker, out io);
ThreadPool.GetMaxThreads(out var workerThreadsMax, out var completionPortThreadsMax);

Console.WriteLine($"Worker thread {workerThreadsMax - worker} Io thread {completionPortThreadsMax - io}");
for (int i = 0; i < 100; i++)
{
                
    Task.Run(() =>
    {
        Console.WriteLine(Thread.CurrentThread.ManagedThreadId + " - Running thread");
        ThreadPool.GetAvailableThreads(out var worker2, out var io2);
        ThreadPool.GetMaxThreads(out var workerThreadsMax2, out var completionPortThreadsMax2);
        Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} - Worker thread {workerThreadsMax2 - worker2} Io thread {completionPortThreadsMax2 - io2}");

        var t1 = Task.Delay(5000);
        var t2 = Task.Delay(5000);
        Task.WaitAll(t1, t2);

        Console.WriteLine(Thread.CurrentThread.ManagedThreadId + " - End of thread");
        ThreadPool.GetAvailableThreads(out worker2, out io2);
        ThreadPool.GetMaxThreads(out workerThreadsMax2, out completionPortThreadsMax2);
        Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} - Worker thread {workerThreadsMax2 - worker2} Io thread {completionPortThreadsMax2 - io2}");
    });
}

Console.ReadLine();

所以我在这段代码中试图做的是 运行 或至少排队 500 个任务(我知道很多,但很好奇),同时仍然显示来自的活动线程数线程池。所以在第一行中,我在 ThreadPool 中有 0 个工作线程,这是有道理的,只要我还没有启动任何任务。但是当第一个任务 运行s 有 8 个活动线程时。这是一件奇怪的事情发生的地方: 每秒或更短时间产生一个新线程(但不是立即产生),这不是真正的问题,但我不明白的是为什么任务被阻止?即使 250 毫秒的延迟完成,任务也不会自行结束,即使超过一分钟,它仍然阻塞在 Task.WaitAll 行上:

Worker thread 0 Io thread 0
Worker thread 0 Io thread 0
8 - Running thread
8 - Worker thread 8 Io thread 0
6 - Running thread
6 - Worker thread 8 Io thread 0
10 - Running thread
10 - Worker thread 8 Io thread 0
7 - Running thread
7 - Worker thread 8 Io thread 0
11 - Running thread
11 - Worker thread 8 Io thread 0
5 - Running thread
9 - Running thread
9 - Worker thread 8 Io thread 0
12 - Running thread
12 - Worker thread 8 Io thread 0
5 - Worker thread 8 Io thread 0
13 - Running thread
13 - Worker thread 9 Io thread 0
14 - Running thread
14 - Worker thread 10 Io thread 0
15 - Running thread
15 - Worker thread 11 Io thread 0
16 - Running thread
16 - Worker thread 12 Io thread 0
17 - Running thread
17 - Worker thread 13 Io thread 0
18 - Running thread
18 - Worker thread 14 Io thread 0

这里有死锁吗?如果有人可以向我解释这一点,那就太好了。谢谢

编辑: 对于那些建议使用 async 和 await Task.WhenAll(..) 的人,你是完全正确的!但正如我所说,这是出于测试目的,我不会在现实中这样做,而是使用 async/await 语句。但是我们正在和一个朋友测试一些关于同步和异步任务的东西,当测试同步方式时我们遇到了这个问题而不知道发生了什么。感谢那些澄清这一点的人。很有启发性。

您的 Task 应该是:

Task.Run(async () =>
    {


        var t1 = Task.Delay(5000);
        var t2 = Task.Delay(5000);
        await Task.WhenAll(t1, t2);


    });

没有 async,该方法是阻塞的。 async/await 使任务成为非阻塞的。如 you need to use the non-blocking Task.WhenAll not, Task.WaitAll. See WaitAll vs WhenAll

所述

让我们更详细地看一下这段代码

var t1 = Task.Delay(5000);
var t2 = Task.Delay(5000);
Task.WaitAll(t1, t2);

Task.Delay 本质上是定时器的包装器,更具体地说是 System.Threading.Timer。该计时器会将实际计时委托给 OS。当计时器结束时,它会在线程池线程上引发偶数事件,线程池线程又将任务标记为已完成。这将触发对 Task.WhenAll 任务的检查,以查看是否可以完成,如果可以则取消阻止。

但是,您的测试本质上是为了耗尽线程池而设计的,从而导致典型的死锁。所有 Task.WhenAll 任务都在等待一个或多个 Task.Delay 完成,但这需要一个可用的线程池线程,但所有线程在等待 Task.WhenAll 任务时都被阻塞。所以一切都在等待别的,什么都不能运行.

除了线程池的设计者预料到了这个问题,并添加了一种增加线程池线程数量的机制,允许 Task.Delay 完成并解决死锁。但是这个机制。因此,任务完成有很大的延迟也就不足为奇了。

由于这是一个玩具示例,可能不需要解决方案,但可能值得重复。不要过度订阅线程池。使用异步、非阻塞代码,或注意使用多少线程的代码。