我有一个服务,我将其作为单例注入,它具有:
private readonly Dictionary<string, List<string>> _fromToAll = [];
private readonly Mutex _mutex = new();
并且我希望字典仅在需要时才延迟初始化一次,例如:
private async Task<Dictionary<string, List<string>>> FindFromToAll()
{
try
{
_mutex.WaitOne();
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
{
_mutex.ReleaseMutex();
}
}
但是当我调用并等待返回字符串列表的 FromTo 任务时,上下文会发生变化,并且当我释放互斥锁时,它会崩溃,因为我从另一个线程释放它,异常消息如下所示:“调用了对象同步方法来自不同步的代码块。”
知道为什么会发生这种情况或者我该如何做不同的事情吗? 预先感谢您😃
即使我添加ConfigureAwait(true)也不会将上下文切换到捕获的上下文。
互斥体具有线程关联性,这意味着每当您将其锁定在一个线程上时,该线程都必须将其解锁。当您
await
没有同步上下文的东西时,延续会在线程池上进行,因此,运行它的代码(以及解锁)与最初锁定它的代码不同的可能性非常高。
解决方案是使用不同的原语。最简单的方法是使用
SemaphoreSlim
,它是异步感知的,初始计数为 1:
private readonly SemaphoreSlim _sem = new SemaphoreSlim(1);
然后,你的代码变成:
try
{
await _sem.WaitAsync();
// rest of the code is unchanged
}
finally
{
_sem.Release();
}
就像
Mutex
一样,SemaphoreSlim
是一次性的,因此请确保在使用完毕后将其丢弃。
作为额外奖励,如果您想要可取消的等待,可以将
CancellationToken
传递给 WaitAsync()
。