多线程等待已运行Task疑似死锁,求正确实现方式
问题分析与正确实现
你的代码存在两个核心问题:
- 多线程重复重置
_runningTask导致的竞态问题:多个等待同一Task的线程,在Task完成后都会执行_runningTask = null。如果其中一个线程刚把_runningTask设为null,新的调用线程立刻进入lock创建新Task,随后另一个等待线程完成await后又把刚创建的新Task设为null,会导致后续调用重复触发API请求。 - 潜在的死锁风险:如果调用
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
相关产品推荐
相关产品推荐

