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

2024-02-08

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

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;
}

我正在编写一个测试,以确保当多个请求想要(大致)同时执行任务时等待相同的任务。我通过嘲笑来做到这一点_service并数数有多少次_service.DoSomething()正在被呼叫。如果调用的话,应该只有一次DoTask(int key)大约是在同一时间制作的。

然而,结果表明,如果我打电话DoTask(int key)多次调用之间的延迟小于 1~2ms,两个任务都将创建并执行其实例SampleTask()用第二个替换字典中的第一个。

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


去引用文档 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使用细粒度锁定来保证线程安全。 (对字典的读取操作是以无锁方式执行的。)但是,valueFactorydelegate 在锁之外调用,以避免在锁下执行未知代码时可能出现的问题。所以,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在这种情况下,您应该考虑自己锁定任务创建。由于它会很快完成(无论您的任务实际需要多长时间才能解决),锁定不会造成太大影响。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

ConcurrentDictionary.GetOrAdd 真的是线程安全的吗? 的相关文章

随机推荐

  • 要求 RMagick 向 ImageMagick 发送直接命令

    RMagick 不支持 ImageMagick 的某些选项 有时使用 ImageMagick 实际上更方便 是否有 Image 对象的 方法允许您使用命令行界面直接向 ImageMagick 发送命令 你用一下可以吗system或通过反引号
  • 在Android中使用导航组件时如何删除默认动画过渡?

    我正在使用导航组件 并且在主要活动中有一个底部导航视图 当我点击底部导航视图中的选项卡时 片段出现时动画似乎会淡出 我不认为我手动设置了动画 似乎动画会默认存在 我想删除那个动画 这是我在主要活动中使用的代码 class MainActiv
  • 按坐标的时区[重复]

    这个问题在这里已经有答案了 正如标题所暗示的 我需要根据一对坐标找到一个时区 或者可能只是 UTC 偏移量 我一直在寻找不同的解决方案 并且有一些网络服务 但我需要能够离线访问应用程序 由于时区并不完全基于经度 这似乎并不那么容易 我想查询
  • 如何使用 jQuery 将

    如何使用 jQuery 移动特定的
  • C# 中带有随机数的二维数组

    我想在 C 中创建二维数组 size 3 on 5 插入随机数 我尝试这样做 但它不起作用 Random rnd new Random int lala new int 3 5 for int i 0 i lt 3 i for int j
  • 如何重新排列 gtsummary 或 flextable 中的列?

    参考这个答案 置信区间 https stackoverflow com a 66891473 13734451 https stackoverflow com a 66891473 13734451 如何重新排列 gtsummary 或 f
  • 如何将 pdftk 添加到 Heroku Cedar 应用程序?

    我需要在 Heroku Cedar Rails 应用程序中将多个 PDF 文件合并为一个 并决定使用 pdftk 来完成此操作 我不知道如何做到这一点 我认为最好的方法是创建一个自定义构建包 其中包含 pdftk 的编译二进制文件 但我不太
  • ruby 中日期的比较

    如何将特定日期与今天进行比较以了解该特定日期是否大于今天 谢谢哈里什 date 2010 07 20 to date 20th July today Date today 21st July if date gt today puts da
  • 在导出 Android 应用程序向导中为 Android 应用程序生成密钥库?

    我正在尝试在 Eclipse 中导出已签名的 Android 应用程序 我认为我正朝着正确的方向前进 右键单击项目 gt 安卓工具 gt 导出已签名的申请包 将出现 导出 Android 应用程序 向导 选择我要导出的项目 点击Next 出
  • 如何使用 Objective C 在 SQLITE 中启用外键约束

    今天我注意到我的 SQLite 表上的外键约束不起作用 在阅读 Stack Overflow 后 我发现应该启用此功能 所以 我正在寻找执行此操作的代码片段 到目前为止 我只能找到这个 self db executeUpdate PRAGM
  • 在VBA中同时设置单元格左边框和右边框

    想知道是否有办法用一条语句设置单元格的左边框和右边框 类似的东西msgBox配置可以组合 添加在一起 例如vbYesNo vbQuestion 我试过 Cells j i Borders xlEdgeLeft xlEdgeRight 这给我
  • 如何使用 SQL Server 数据库中的值填充列表?

    该列表将根据我的数据库中有多少项目而增长和缩小 我需要填充列表而不是列表框 我知道我需要打开一个连接 using var conn new SqlConnection Properties Settings Default DBConnec
  • java bean如何将多个对象封装成一个

    在定义中说 java bean将许多对象封装成一个对象 Bean 这里的 许多对象 是什么意思以及java bean如何将它们封装成一个对象 None
  • 如何在 python 中使用 ipython 笔记本 Markdown 单元格的内容

    在 IPython 中 我们可以通过以下方式获取先前的输出和输入Out n and In n 变量 是否可以使用 Markdown 笔记本单元的内容并在 python 中使用它 我想在 Markdown 单元格中写入一些文本 This is
  • TouchsMoved 以不规则的间隔调用

    我正在为 iOS 制作一款游戏 您主要在屏幕上拖动大对象 当我在实际的 iPad iPhone 上运行游戏一段时间 连续在屏幕上画圈拖动对象 时 每隔 5 分钟左右拖动的对象会卡顿约 10 30 秒 然后 它又恢复如丝般光滑的移动状态 从视
  • Delphi中如何区分多个键盘?

    我的电脑上连接了两个键盘 一个用于输入 TMemo1 另一个用于输入 TMemo2 两者都可以同时打字 问题是我无法区分键盘一输入的内容和键盘二输入的内容 有没有办法区分某些输入来自哪个设备 Dian 你可以使用注册原始输入设备 http
  • struct.error:解包需要长度为 16 的字符串参数

    处理 PDF 时文件 2 pdf https yadi sk i 2vABlTaexZerg使用 pdfminer pdf2txt py 我收到以下错误 pdf2txt py 2 pdf Traceback most recent call
  • 以编程方式将产品添加到购物车并更改价格

    我想以编程方式将产品添加到购物车 另外 我想在添加到购物车时更改产品价格 假设我的产品价格是 100 美元 添加到购物车后我想将其更改为 90 美元 我将产品添加到购物车 但是 我无法更改产品价格 是否可以 以下是将产品添加到购物车的代码
  • 使用 nginx/gunicorn 进行 Django 文件上传 - 媒体权限

    我试图允许 django 站点的用户通过模型上的 FileField 将文件 主要是 PDF 上传到我的服务器 但是 当我尝试使用我的模型表单生成的上传字段时 我不断遇到 Errno 13 Permission Denied 我在四处寻找时
  • ConcurrentDictionary.GetOrAdd 真的是线程安全的吗?

    我有这段代码 如果该任务是为相同的输入创建的 我想等待正在进行的任务 这是我正在做的事情的最小再现 private static ConcurrentDictionary