You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

单例信息服务异步计算方案咨询:并发与重入问题排查

方案可行性与并发问题咨询

我实现了一个管理InformationBlock的单例组件SingletonInformationService,InformationBlock由Id和时间段标识,计算耗时数秒并存储于集合中。设计流程为:首个请求触发计算,后续请求等待计算完成后统一回调,核心代码如下:

public class SingletonInformationService
{
    private readonly Collection<InformationBlock> blocks = new();
    private object syncObject = new();

    public void GetInformationBlock(Guid id, TimePersiod timePeriod,
        Action<InformationBlock> callOnFinish)
    {
        InformationBlock block = null;
        
        lock(syncObject)
        {
            // check out if the block already exists
            block = blocks.SingleOrDefault(b => b.Id ...);
            
            if (block == null)
            {
                block = new InformationBlock(...);
                blocks.Add(block);
            }
        }
        
        block?.BeginCalculation(callOnFinish);
        
        return true;
    }
} 

public class InformationBlock
{
    private Task calculationTask = null;
    private CalculationState isCalculating = CalculationState.Unknown;
    private List<Action<InformationBlock>> waitingRoom = new();
    
    internal void BeginCalculation(Action<InformationBlock> callOnFinish)
    {
            if (isCalculating == CalculationState.Finished)
            {
                callOnFinish(this);
                return;
            }
            else if (isCalculating == CalculationState.IsRunning)
            {
                waitingRoom.Add(callOnFinish);
                return;
            }

            // add the first call to the waitingRoom
            waitingRoom.Add(callOnFinish);
                
            isCalculating = CalculationState.IsRunning;
            
            calculationTask = Task.Run(() => { // run the calculation})
                    .ContinueWith(taskResult => 
                    {
                        //.. apply the calculation result to local properties
                        this.Property1 = taskResult.Result.Property1;
                        
                        // set the state to mark this instance as complete
                        isCalculating = CalculationState.Finished;
                                                
                        // inform all calls about the result
                        waitingRoom.ForEach(c => c(this));
                                                
                        waitingRoom.Clear();
                    }, TaskScheduler.FromCurrentSynchronizationContext());
    }
}

现咨询该方案是否可行,是否存在故障、死锁风险;BeginCalculation被并发调用时是否需要await calculationTask?


方案可行性与风险分析

可行性

整体设计思路是可行的:通过单例统一管理InformationBlock实例,避免重复计算,后续请求排队等待结果统一回调,能有效减少重复计算开销,符合业务需求。

潜在故障与死锁风险

  • 线程安全隐患:InformationBlock中的isCalculating状态切换、waitingRoom的增删操作未加锁,并发调用BeginCalculation时会出现竞态条件——比如多个线程同时进入Unknown状态分支,重复启动计算任务;List<Action<InformationBlock>>本身不是线程安全集合,并发添加会导致数据损坏。
  • 死锁风险:ContinueWith使用TaskScheduler.FromCurrentSynchronizationContext(),如果当前是UI同步上下文(如WPF/WinForms),计算任务完成后回调会被调度到UI线程,若回调逻辑中再次触发GetInformationBlock并进入lock,可能和UI线程调度冲突引发死锁;另外,若计算任务阻塞,同步上下文队列积压也会导致响应停滞。
  • 异常未处理:如果Task.Run中的计算抛出异常,taskResult.IsFaulted状态被忽略,isCalculating会一直停留在IsRunning,所有后续请求都会被加入waitingRoom但永远得不到回调,形成“请求黑洞”。
  • 内存泄漏:blocks集合只增不减,若Id和时间段组合过多,会导致内存持续上涨;若计算任务异常终止,waitingRoom中的委托会持有外部引用,无法被GC回收。

BeginCalculation是否需要await calculationTask?

不需要直接await,但要优化任务的异常处理和状态管理:

  • 当前代码仅赋值calculationTask,未跟踪任务异常,需在ContinueWith中判断taskResult.Status,处理Faulted和Canceled状态,避免isCalculating卡住。
  • 若将BeginCalculation改为异步方法(返回Task),可让调用方感知计算状态,但核心逻辑无需await——我们需要的是后台计算完成后统一通知所有等待回调,而非让单个调用方等待任务结束。

内容的提问来源于stack exchange,提问作者zimmybln

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.24 18:18:29