如何在当前线程上启动Task(强制内联)

本文关键字:Task 启动 前线 线程 | 更新日期: 2023-09-27 18:21:13

情况如下:在WPF应用程序中,我有一个计算管道,其中充满了进行不同计算的小节点。事实上,它是这些节点的树,其计算依赖于其他节点的计算结果。当依赖项发生变化时,每个节点都将通过启动任务来触发重新计算。如果我的图中有1000个节点。当树底部的某个节点发生变化时,可能所有节点都需要重新计算,每个节点都启动一个任务,等待其子节点的结果完成。

症状:在某些情况下,计算管道似乎"悬空",需要很长时间才能进行简单计算(通常不到一秒钟,但可能需要30秒到15分钟)通过分析,我注意到CPU非常可用,并且所有线程都在等待子节点的结果。当有断点时,没有人在进行计算。

在我对ThreadPool和TaskScheduler所知有限的情况下,似乎正在执行的任务在队列中相距遥远,因此每个人都在等待。看起来不像是僵局,因为它会在某个时候恢复。我想我需要启动更少的任务,或者将ThreadPool的最小线程数提高到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版本,它会启动一个新的Task并等待结果。我们这样做是为了,如果我们有一个同步的"Get"调用,然后是一个异步调用(在同步调用完成之前),我们希望返回同步任务的结果。

基本用法来自UI线程,我们调用顶级计算节点的异步版本(很少调用),而这些节点在任务中将调用同步版本。

因此,我的问题来源:
-假设一个节点的任务已经在与UI线程不同的线程中,并且它请求子节点的结果,我可以要求将该子节点的任务内联在当前线程中而不是调度它吗?从而减少发送到TaskScheduler的任务数量?

或者还有其他想法吗?或者我完全没有抓住要点?!

如何在当前线程上启动Task(强制内联)

向调度程序发送任务分配并不是问题所在,问题是(如果我正确理解您的场景)让异步代码同步阻塞以等待另一个异步调用的结果,事实上,如果线程池耗尽,这可能会导致死锁。

我的建议是使_computationFunc的类型为Func<Task<T>>,并且它将立即消除在子节点内调用同步GetOrComputeValue的需要。如果子(叶)节点不需要是异步计算,您可以简单地使用Task.FromResult 返回其结果

我还建议您使用asyncawait编码模式,使您的生活更加轻松。

编辑:关于你关于在当前线程上内联任务的问题:在我看来,如果你遵循我的建议,你应该不需要这样做。但是,为了内联一个任务,需要编写自己的任务调度程序,但即使这样,您也只能内联尚未执行的任务(不能内联已在运行的任务)