情况如下: 我在WPF应用程序中有一个计算管道,它充满了小节点,可以进行不同的计算。它实际上是那些节点的树,其计算取决于其他节点的计算结果。每个节点在其依赖关系发生更改时,将通过启动任务来触发重新计算。所以,如果我的图表中有1000个节点。当其中一个节点在树的底部发生变化时,可能会发生所有节点需要重新计算,每个节点都启动一个任务并等待其子节点的结果完成。
症状: 在某些情况下,计算管道似乎是“悬挂”并花费很长时间进行简单计算(通常需要不到一秒钟,但可能需要30秒到15分钟) 通过分析,我注意到CPU非常可用,并且所有线程都只是在等待子节点的结果。有断点时没人在做计算。
在我对ThreadPool和TaskScheduler的有限知识中,似乎正在进行工作的任务在队列中很远,因此每个人都在等待。看起来不像死锁,因为它会在某个时刻恢复。 我想我需要开始减少任务,或者将ThreadPool的min线程数增加到400,然后问题消失(但我更喜欢第一个解决方案)
以下是我如何请求节点结果的快速浏览(不是实际代码,因为我的线程安全和基本管道更大)。
public T GetOrComputeValue()
{
return GetOrComputeValueAsync().Result;
}
public Task<T> GetOrComputeValueAsync()
{
// If we are not flagged as dirty, then we can return the last
// computation-task, which is either waiting to be started yet,
// still busy computing or might already have finished long ago.
if (!IsDirty && (_computationTask != null))
return _pendingRecomputationTask ?? _computationTask;
IsDirty = false;
_computationTask = Task.Run( _computationFunc);
}
请注意,同步调用只会调用Async版本,它会启动一个新任务并等待结果。我们就是这样做的,这样如果我们有一个同步的“Get”调用,然后是异步调用(在同步调用完成之前),我们想要返回Synchronous Task的结果。
基本用法来自UI线程,我们称之为顶级计算节点的异步版本(非常少的调用),这些节点在任务中将调用同步版本。
所以,我的问题的根源:
- 假设节点的任务已经在与UI线程不同的线程中,并且它请求子节点的结果,我可以要求在当前线程中内联该子节点的任务而不是调度它吗?从而减少发送到TaskScheduler的任务数量?
还是其他任何想法? 或者我完全忽略了这一点?!
答案 0 :(得分:2)
您要向调度程序发送大量任务不是问题,问题是您(如果我理解您的方案正确)让异步代码同步阻塞等待另一个异步调用的结果,实际上,如果你的线程池耗尽,可能会导致死锁。
我的建议是让_computationFunc
属于Func<Task<T>>
类型,它会立即消除在子节点内调用同步GetOrComputeValue
的需要。如果子(叶)节点不需要是异步计算,您可以使用Task.FromResult
我还建议您使用async
和await
编码模式让您的生活更轻松。
修改强> 关于你在当前主题上内联任务的问题:在我看来,如果你遵循我的建议你就不应该这样做。但是为了内联任务需要writing your own task scheduler,但即便如此,你也只能内联尚未执行的任务(你不能内联已经运行的东西)