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

多线程等待已运行Task疑似死锁,求正确实现方式

问题分析与正确实现

你的代码存在两个核心问题:

  1. 多线程重复重置_runningTask导致的竞态问题:多个等待同一Task的线程,在Task完成后都会执行_runningTask = null。如果其中一个线程刚把_runningTask设为null,新的调用线程立刻进入lock创建新Task,随后另一个等待线程完成await后又把刚创建的新Task设为null,会导致后续调用重复触发API请求。
  2. 潜在的死锁风险:如果调用CanExecute的线程带有同步上下文(如UI线程),且await时未使用ConfigureAwait(false),Task完成后会尝试回到原上下文,若上下文被阻塞则会引发死锁。

正确实现代码

private readonly object _lockobj = new object();
private Task<bool> _runningTask;

protected async Task<bool> CanExecute([CallerMemberName] string memberName = "")
{
    Task<bool> currentTask;
    
    // 线程安全地获取或创建Task
    lock (_lockobj)
    {
        if (_runningTask == null)
        {
            _runningTask = CanExecuteTask(memberName);
        }
        currentTask = _runningTask;
    }

    // 等待Task完成,避免捕获同步上下文防止死锁
    var result = await currentTask.ConfigureAwait(false);
    
    // 仅当当前_runningTask仍是我们等待的那个Task时,才重置为null
    lock (_lockobj)
    {
        if (_runningTask == currentTask)
        {
            _runningTask = null;
        }
    }
    
    return result;
}

private async Task<bool> CanExecuteTask(string callingMemberName)
{
    // 实际API调用和其他逻辑,同样使用ConfigureAwait(false)避免上下文捕获
    var result = await SomeHttpCallAsync().ConfigureAwait(false);
    // 其他操作...
    return result;
}

关键优化点说明

  • 线程安全的Task管理:所有对_runningTask的读取和赋值都在lock块内执行,确保内存可见性和操作原子性。每个等待线程会先保存当前要等待的Task实例,避免后续误重置新创建的Task。
  • 避免死锁:所有await操作添加ConfigureAwait(false),防止Task完成后尝试回到原同步上下文,适用于后台定时器这类无UI上下文的场景。
  • 防止重复API调用:只有当当前完成的Task仍是全局_runningTask时才重置为null,确保新的调用只会在没有运行中Task时才创建新实例。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 06:45:22