【发布时间】:2017-07-16 03:22:46
【问题描述】:
前言:我知道使用 ThreadPool(通过 TPL 或直接)进行 IO 操作 is generally frowned upon 因为 IO 必须是顺序的,但是我的问题与阻塞调用的“并行 IO”有关公开一个Async 方法。
我正在编写一个 GUI 工具,用于获取有关网络上执行此操作的计算机的信息(简化代码):
String[] computerNames = { "foo", "bar", "baz" };
foreach(String computerName in computerNames) {
Task.Factory
.StartNew( GetComputerInfo, computerName )
.ContinueWith( ShowOutputInGui, RunOnGuiThread );
}
private ComputerInfo GetComputerInfo(String machineName) {
Task<Int64> pingTime = Task.Factory.StartNew( () => GetPingTime( machineName ) );
Task<Process[]> processes = Task.Factory.StartNew( () => System.Diagnostics.Process.GetProcesses( machineName ) );
// and loads more
Task.WaitAll( pingtime, processes, etc );
return new ComputerInfo( pingTime.Result, processes.Result, etc );
}
当我运行这段代码时,我发现与我使用的旧顺序代码相比,它的运行时间长得惊人。
请注意,GetComputerInfo 方法中的每个任务完全独立于它周围的其他任务(例如 Ping 时间可以与 GetProcesses 分开计算),但是当我插入一些 Stopwatch 计时调用时,我发现个人子任务,例如 GetProcesses 调用仅在调用 GetComputerInfo 之后才开始到 3000ms - 存在一些很大的延迟。
我注意到,当我将外部并行调用的数量减少到GetComputerInfo(通过减少computerNames 数组的大小)时,几乎立即返回了第一个结果。一些计算机名称是针对已关闭的计算机的,因此称为GetProcesses 和PingTime 需要很长时间才能超时(我的真实代码会捕获异常)。这可能是因为离线计算机阻止了 Tasks 的运行,而 TPL 自然将其限制为我的 CPU 硬件线程数 (8)。
有没有办法告诉 TPL 不要让内部任务(例如GetProcesses)阻塞外部任务(GetComputerInfo)?
(我已经尝试过“父/子”任务附件/阻止,但它不适用于我的情况,因为我从未明确地将子任务附加到父任务,并且父任务自然会等待Task.WaitAll )。
【问题讨论】:
-
如果
GetComputerInfo()里面没有Task.WaitAll()可能会更好......有点违背了目的。为什么不返回Task[]? -
如果没有一个好的minimal reproducible example 可靠地重现问题,即使不是不可能,也很难诊断问题。也就是说,请记住,线程池并没有无限数量的线程在等待您。在空闲时,它最多只有少数线程(等于 CPU 内核数),并且只会每半秒到一秒启动新线程(IIRC 这是可配置的,我不记得默认值)。您可以通过 a) 使用异步 ping 方法和 b) 使用
ThreadPool.SetMinThreads()来增加空闲线程的数量,以便它们在您需要时准备好。 -
请注意,调用
SetMinThreads()有点小技巧。不幸的是 .NET 没有GetProcesses()方法的异步版本;但我什至看不到在本机代码中执行此操作的异步方式。如果您真的希望这些操作是并行的,我认为您将不得不自己管理线程。 -
顺便说一句,您不使用
Task.Run()和await的任何原因? -
@PeterDuniho
await电话会去哪里?
标签: c# multithreading task-parallel-library