await切换上下文致Mutex释放异常,单例服务懒加载求解
问题原因
Mutex是线程绑定的同步原语:必须由调用WaitOne()获取锁的线程来调用ReleaseMutex()释放锁。而await关键字会在异步操作完成后,可能调度到不同的线程继续执行后续代码(即使使用ConfigureAwait(true),也无法保证一定回到原线程——比如原线程已被回收、或上下文不存在时,仍会切换到线程池其他线程)。此时finally块中的ReleaseMutex()由非获取锁的线程执行,就会触发上述异常。
替代实现方案
方案1:使用SemaphoreSlim(支持异步等待的同步原语)
SemaphoreSlim不绑定线程,支持异步等待,适合异步场景下的线程安全控制:
// 替换原Mutex成员 private readonly SemaphoreSlim _semaphore = new(1, 1); private readonly Dictionary<string, List<string>> _fromToAll = []; private async Task<Dictionary<string, List<string>>> FindFromToAll() { await _semaphore.WaitAsync(); try { if (_fromToAll.Count > 0) { return _fromToAll; } foreach (var serviceType in FindAllServiceTypes()) { var service = ActivatorUtilities.CreateInstance(provider, serviceType); var fromTo = await service.Invoke<Task<List<string>>>("FromTo"); _fromToAll.Add(serviceType.Name.Replace("Service", null), fromTo); } return _fromToAll; } finally { _semaphore.Release(); } }
方案2:使用Lazy<Task<T>>实现异步延迟初始化
利用Lazy<T>的线程安全特性(默认模式下LazyThreadSafetyMode.ExecutionAndPublication,保证初始化只执行一次),结合异步任务完成初始化:
private readonly Lazy<Task<Dictionary<string, List<string>>>> _lazyFromToAll; // 在服务构造函数中初始化Lazy public YourSingletonService(IServiceProvider provider) { _lazyFromToAll = new Lazy<Task<Dictionary<string, List<string>>>>(async () => { var result = new Dictionary<string, List<string>>(); foreach (var serviceType in FindAllServiceTypes()) { var service = ActivatorUtilities.CreateInstance(provider, serviceType); var fromTo = await service.Invoke<Task<List<string>>>("FromTo"); result.Add(serviceType.Name.Replace("Service", null), fromTo); } return result; }); } private Task<Dictionary<string, List<string>>> FindFromToAll() { return _lazyFromToAll.Value; }
这种方式无需手动管理锁,Lazy<T>会自动处理线程安全,且初始化逻辑仅执行一次。
方案3:服务启动时提前初始化
如果业务允许,可在服务启动阶段完成字典初始化,避免运行时的异步锁问题:
// 在服务构造函数或启动逻辑中执行 public YourSingletonService(IServiceProvider provider) { _fromToAll = InitializeFromToAllAsync(provider).GetAwaiter().GetResult(); } private async Task<Dictionary<string, List<string>>> InitializeFromToAllAsync(IServiceProvider provider) { var result = new Dictionary<string, List<string>>(); foreach (var serviceType in FindAllServiceTypes()) { var service = ActivatorUtilities.CreateInstance(provider, serviceType); var fromTo = await service.Invoke<Task<List<string>>>("FromTo"); result.Add(serviceType.Name.Replace("Service", null), fromTo); } return result; }
注意:此方式会延长服务启动时间,需根据业务场景评估是否可接受。
内容的提问来源于stack exchange,提问作者Gion Ion
相关产品推荐
相关产品推荐

