C# ConcurrentDictionary.GetOrAdd真的是线程安全的吗?

C# ConcurrentDictionary.GetOrAdd真的是线程安全的吗?,c#,task,C#,Task,我有一段代码,我想等待正在进行的任务,如果该任务是为相同的输入创建的。这是我正在做的最小复制 私有静态ConcurrentDictionary\u tasks=new ConcurrentDictionary(); 专用只读外部服务\u服务; 公共异步任务SampleTask(){ var result=wait_service.DoSomething(); 等待任务。延迟(1000)//完成此任务需要一些时间 返回结果; } 公共异步任务点任务(int键){ var task=_tasks.G

我有一段代码,我想等待正在进行的任务,如果该任务是为相同的输入创建的。这是我正在做的最小复制

私有静态ConcurrentDictionary\u tasks=new ConcurrentDictionary();
专用只读外部服务\u服务;
公共异步任务SampleTask(){
var result=wait_service.DoSomething();
等待任务。延迟(1000)//完成此任务需要一些时间
返回结果;
}
公共异步任务点任务(int键){
var task=_tasks.GetOrAdd(键,=>SampleTask());
var taskResult=等待任务;
_任务.TryRemove(键,输出任务);
返回任务结果;
}
我正在编写一个测试,以确保当多个请求希望在(大致)相同的时间执行任务时,等待相同的任务。我通过模拟
\u service
并计算调用
\u service.DoSomething()
的次数来实现这一点。如果对
DoTask(int key)
where的调用几乎同时进行,则只应执行一次

但是,结果表明,如果我多次调用
DoTask(int key)
,调用之间的延迟小于1~2ms,这两个任务都将在
SampleTask()
实例上创建并执行它,第二个任务将取代字典中的第一个任务

考虑到这一点,我们可以说这种方法是真正的线程安全的吗?或者我的问题本身不是线程安全问题吗?

引用(强调我的问题):

对于字典的修改和写入操作,使用细粒度锁定来确保线程安全。(字典上的读取操作是以无锁方式执行的。)但是,在锁之外调用
valueFactory
委托,以避免在锁下执行未知代码时可能出现的问题。因此,对于
ConcurrentDictionary
类上的所有其他操作,它不是原子的

由于键/值可以在
valueFactory
生成值时由另一个线程插入,因此不能仅仅因为执行了
valueFactory
就相信它生成的值会被插入字典并返回如果在不同线程上同时调用
GetOrAdd
,则可以多次调用
valueFactory
,但只会向字典中添加一个键/值对。

因此,虽然字典是线程安全的,但在您的情况下,对
valueFactory
\u=>SampleTask()
的调用不能保证是唯一的。所以你的工厂功能应该能够接受这个事实

您可以确认这一点:

public TValue GetOrAdd(TKey-key,Func-valueFactory)
{
如果(key==null)抛出新的ArgumentNullException(“key”);
如果(valueFactory==null)抛出新的ArgumentNullException(“valueFactory”);
t值结果值;
if(TryGetValue(键,输出结果值))
{
返回结果值;
}
TryAddInternal(键、值工厂(键)、false、true、out结果值);
返回结果值;
}
如您所见,
valueFactory
在负责正确锁定字典的
TryAddInternal
外部被调用

但是,由于
valueFactory
是一个lambda函数,它在您的案例中返回一个任务(
\u=>SampleTask()
),并且字典本身不会等待该任务,因此该函数将快速完成,在遇到第一个
等待
(设置异步状态机时)。因此,除非调用一个接一个地快速进行,否则任务应快速添加到字典中,后续调用将重用同一任务


如果你在所有的情况下都只需要发生一次,那么你应该考虑自己锁定任务创建。因为它很快就会完成(不管你的任务实际需要花多长时间来解决),锁定不会有那么大的伤害。

当你调用<代码> GETORADD(),在某些情况下,您传递给它的委托可以被多次调用。这就是您看到的吗?您也没有在等待
SampleTask()
@MatthewWatson,那是什么情况?@Tseng,我需要在工厂代表处等待它吗?我正在等待它分配给
任务后variable@LuísGabrieldeAndrade您首先要做什么?
ConcurrentDictionary
是线程安全的,但这并不意味着它包含的对象AIN是。它也不是任务队列。如果您想处理多条消息,您可能应该使用
ActionBlock
或TPL DataflowBlock库中的其他类。另一个好的选择是使用
System.Threading.Channels
来实现发布/订阅工作。