带有 WaitAll 的 Task.Run 被阻塞

Shi*_*let -1 c# task async-await

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

这是有问题的代码:

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();
Run Code Online (Sandbox Code Playgroud)

所以我在这段代码中尝试做的是运行或至少排队 500 个任务(我知道很多,但很好奇),同时仍然显示线程池中的活动线程数。因此,在第一行中,只要我还没有启动任何任务,线程池中就有 0 个工作线程,这是有道理的。但是当第一个任务运行时,有 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
Run Code Online (Sandbox Code Playgroud)

这里是否发生了僵局?如果有人可以向我解释这一点,那就太好了。谢谢。

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

Jon*_*asH 6

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

var t1 = Task.Delay(5000);
var t2 = Task.Delay(5000);
Task.WaitAll(t1, t2);
Run Code Online (Sandbox Code Playgroud)

Task.Delay本质上是一个计时器的包装器,更具体地说是一个System.Threading.Timer. 该计时器将实际的计时委托给操作系统。当计时器到时,它将在线程池线程上引发偶数,从而将任务标记为已完成。这将触发对任务的检查,Task.WhenAll以查看是否可以完成,如果可以则取消阻止。

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

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

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