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
完成并解决死锁。但是这个机制慢。因此,任务完成有很大的延迟也就不足为奇了。
由于这是一个玩具示例,可能不需要解决方案,但可能值得重复。不要过度订阅线程池。使用异步、非阻塞代码,或注意使用多少线程的代码。
我来这里是因为我在使用这段代码时有一个奇怪的行为: 但在那之前,我知道这样做是一个非常糟糕的做法,所以它甚至没有在现实中使用我只是想了解幕后发生的事情,但我的知识真的很差。
这是有问题的代码:
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
使任务成为非阻塞的。如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
完成并解决死锁。但是这个机制慢。因此,任务完成有很大的延迟也就不足为奇了。
由于这是一个玩具示例,可能不需要解决方案,但可能值得重复。不要过度订阅线程池。使用异步、非阻塞代码,或注意使用多少线程的代码。