Web API令牌并发更新问题及Mutex同步报错解决方案咨询
问题分析与解决方案
错误原因
你遇到的Object synchronization method was called from an unsynchronized block of code错误,核心原因是**Mutex与async/await不兼容**:
- 调用
mutex.WaitOne()的线程会获取Mutex的所有权,但await AccessTokenEndpointCallAsync()会触发线程切换,后续执行finally块的线程已不是最初获取Mutex的线程。 Mutex强制要求必须由获取它的线程调用ReleaseMutex(),跨线程释放就会触发该错误。- 此外,你的场景是单进程内的并发控制,
Mutex作为跨进程同步原语属于过度使用,应该用更轻量、支持异步的同步工具。
正确解决方案:使用SemaphoreSlim实现异步安全的双重检查锁
SemaphoreSlim支持异步等待(WaitAsync()),完美适配async/await场景,同时能实现单进程内的并发控制。修改后的代码如下:
// 在类级别声明SemaphoreSlim实例,初始计数设为1(模拟互斥锁) private readonly SemaphoreSlim _tokenLock = new SemaphoreSlim(1, 1); public async Task<string> GetAccessTokenAsync() { // 首次检查令牌状态,避免不必要的锁竞争 if (!_storage.IsTokenExpired()) { return _storage.ReadAccessToken(); } try { // 异步等待获取锁,不阻塞线程 await _tokenLock.WaitAsync(); // 二次检查令牌:防止等待锁期间已有其他线程更新了令牌 if (!_storage.IsTokenExpired()) { return _storage.ReadAccessToken(); } // 获取新令牌并更新缓存 var newToken = await AccessTokenEndpointCallAsync(); _storage.StoreAccessToken(newToken); return newToken; } finally { // 无论是否发生异常,都释放锁 _tokenLock.Release(); } }
关键优化点
- 异步安全:
SemaphoreSlim.WaitAsync()适配async/await的线程切换特性,释放锁的操作不依赖原线程,不会触发同步错误。 - 双重检查锁:两次验证令牌状态,既减少了无意义的锁竞争,又避免了等待锁期间令牌被其他线程更新的问题。
- 轻量高效:
SemaphoreSlim是单进程内的同步工具,比Mutex性能更高、资源开销更小。
额外注意事项
- 确保
_tokenLock是类的实例或静态成员,不要在方法内部声明,否则锁会完全失效。 - 如果你的服务是多进程部署(比如负载均衡的多个实例),单进程内的锁无法解决跨进程的令牌竞争问题,此时需要结合Redis分布式锁(如Redlock)实现跨进程同步。
内容的提问来源于stack exchange,提问作者riki
相关产品推荐
相关产品推荐

