去引用文档 https://learn.microsoft.com/en-us/dotnet/api/system.collections.concurrent.concurrentdictionary-2.getoradd?view=netframework-4.7.2#System_Collections_Concurrent_ConcurrentDictionary_2_GetOrAdd__0_System_Func__0__1__(强调我的):
对于字典的修改和写入操作,ConcurrentDictionary<TKey,TValue> https://learn.microsoft.com/en-us/dotnet/api/system.collections.concurrent.concurrentdictionary-2?view=netframework-4.7.2使用细粒度锁定来保证线程安全。 (对字典的读取操作是以无锁方式执行的。)但是,valueFactory
delegate 在锁之外调用,以避免在锁下执行未知代码时可能出现的问题。所以,GetOrAdd https://learn.microsoft.com/en-us/dotnet/api/system.collections.concurrent.concurrentdictionary-2.getoradd?view=netframework-4.7.2对于所有其他操作来说不是原子的ConcurrentDictionary<TKey,TValue>
class.
由于键/值可以由另一个线程插入valueFactory
正在产生价值,你不能仅仅因为它就相信它valueFactory
执行后,其产生的值将被插入到字典中并返回。如果你打电话GetOrAdd
同时在不同线程上,valueFactory
可能会被调用多次,但只会将一个键/值对添加到字典中。
因此,虽然字典是正确的线程安全的,但调用valueFactory
, or _ => SampleTask()
就您而言,不能保证是唯一的。所以你的工厂函数应该能够接受这个事实。
您可以确认这一点从源头 https://referencesource.microsoft.com/#mscorlib/system/Collections/Concurrent/ConcurrentDictionary.cs,1059:
public TValue GetOrAdd(TKey key, Func<TKey, TValue> valueFactory)
{
if (key == null) throw new ArgumentNullException("key");
if (valueFactory == null) throw new ArgumentNullException("valueFactory");
TValue resultingValue;
if (TryGetValue(key, out resultingValue))
{
return resultingValue;
}
TryAddInternal(key, valueFactory(key), false, true, out resultingValue);
return resultingValue;
}
如你看到的,valueFactory
正在被外部调用TryAddInternal
它负责正确锁定字典。
然而,自从valueFactory
是一个 lambda 函数,它在您的情况下返回一个任务(_ => SampleTask()
),并且字典不会等待该任务本身,该函数将完成quickly并返回不完整的Task
遇到第一个之后await
(当异步状态机设置时)。因此,除非调用非常快地接二连三,否则任务应该很快添加到字典中,并且后续调用将重用相同的任务。
如果您要求这种情况只发生一次all在这种情况下,您应该考虑自己锁定任务创建。由于它会很快完成(无论您的任务实际需要多长时间才能解决),锁定不会造成太大影响。