我有这段代码,如果该任务是为相同的输入创建的,我想等待正在进行的任务。这是我正在做的事情的最小再现。
private static ConcurrentDictionary<int, Task<int>> _tasks = new ConcurrentDictionary<int, Task<int>>();
private readonly ExternalService _service;
public async Task SampleTask(){
var result = await _service.DoSomething();
await Task.Delay(1000) //this task takes some time do finish
return result;
}
public async Task<int> DoTask(int key) {
var task = _tasks.GetOrAdd(key, _ => SampleTask());
var taskResult = await task;
_tasks.TryRemove(key, out task);
return taskResult;
}
Run Code Online (Sandbox Code Playgroud)
我正在编写一个测试,以确保当多个请求想要(大致)同时执行任务时等待相同的任务。我通过模拟并计算被调用的_service
次数来做到这一点。_service.DoSomething()
如果对 where 的调用DoTask(int key)
大致在同一时间进行,则应该只进行一次。
然而,结果表明,如果我DoTask(int key)
多次调用且调用之间的延迟小于 1~2 毫秒,则两个任务都将创建并执行其实例,SampleTask()
其中第二个任务将替换字典中的第一个任务。
考虑到这一点,我们可以说这个方法是真正的线程安全的吗?或者我的问题本身不是线程安全的情况吗?
pok*_*oke 10
引用文档(强调我的):
对于字典的修改和写入操作,
ConcurrentDictionary<TKey,TValue>
使用细粒度锁定来保证线程安全。(字典上的读取操作以无锁方式执行。)但是,valueFactory
委托是在锁之外调用的,以避免在锁下执行未知代码时可能出现的问题。因此,GetOrAdd
对于ConcurrentDictionary<TKey,TValue>
类上的所有其他操作而言,它不是原子的。由于键/值可以在
valueFactory
生成值时由另一个线程插入,因此您不能相信仅仅因为valueFactory
执行,其生成的值就会被插入到字典中并返回。如果在不同线程上同时调用,可能会被调用多次,但只会将一个键/值对添加到字典中。GetOrAdd
valueFactory
因此,虽然字典是正确的线程安全的,但对valueFactory
或_ => SampleTask()
在您的情况下的调用不能保证是唯一的。所以你的工厂函数应该能够接受这个事实。
您可以从来源确认这一点:
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;
}
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,在负责正确锁定字典的valueFactory
外部调用。TryAddInternal
但是,由于valueFactory
是一个在您的情况 ( _ => SampleTask()
) 中返回任务的 lambda 函数,并且字典不会等待该任务本身,因此该函数将快速Task
完成,并在遇到第一个任务后返回不完整的任务await
(当设置异步状态机时) )。因此,除非调用非常快地接二连三,否则任务应该很快添加到字典中,并且后续调用将重用相同的任务。
如果您要求这种情况在所有情况下都只发生一次,您应该考虑自己锁定任务创建。由于它会很快完成(无论您的任务实际需要多长时间才能解决),锁定不会造成太大影响。