【问题标题】:How should I "Cancel" an AddOrUpdate within ConcurrentDictionary?我应该如何在 ConcurrentDictionary 中“取消” AddOrUpdate?
【发布时间】:2012-11-18 23:00:59
【问题描述】:

我已经阅读了 MSDN 文档和this blog,我需要以下逻辑:

对于ConcurrentDictionary<string,bool>

  1. 如果字符串不存在,添加它,并确保我在添加时将布尔值设置为True
  2. 如果字符串确实存在,则仅将布尔值更改为True,如果它是假的。否则取消更新

我的用例

我有几个 DNS 域要扫描恶意软件。我实时检索的列表中很可能会有重复项。我以 100 个或更少的批次收到 DNS 域列表,将有超过 10,000 个域进行扫描。

我只想在每次迭代 10,000 个域时扫描一次 DNS 主机。 bool == true 表示它当前正在被扫描,我应该在继续之前取消任务。 bool == false 或没有条目意味着我应该立即将条目更新为 bool==true 或尽快创建一个新条目。

记住...

AddOrUpdate 将被独立于 .NET4 的 TPL 中的许多独立线程调用。每个线程都需要决定它是否需要处理 Dictionary 的 key... 中提到的值,还是继续下一个。应该只对一个“键”进行处理。

我需要通知调用线程更新成功或失败。另外according to this answer好像AddOrUpdate的函数会被调用很多次。我认为这可能意味着我的调用线程会因为取消key 上的工作或继续它而感到困惑。 (记住只有一个线程可以在key上积极工作

可能混淆调用线程的并发更新示例

ConcurrentDictionary<int, string> numbers = new ConcurrentDictionary<int, string>();
Parallel.For(0, 10, x =>
{
    numbers.AddOrUpdate(1,
        i =>
        {
            Console.WriteLine("addValueFactory has been called");
            return i.ToString();
        },
        (i, s) =>
        {
            Console.WriteLine("updateValueFactory has been called");
            return i.ToString();
        });
});

输出

addValueFactory has been called
addValueFactory has been called
addValueFactory has been called
addValueFactory has been called
updateValueFactory has been called
updateValueFactory has been called
updateValueFactory has been called
updateValueFactory has been called
updateValueFactory has been called
updateValueFactory has been called
updateValueFactory has been called
updateValueFactory has been called
updateValueFactory has been called

问题

我应该如何将这个“取消更新”功能添加到 AddOrUpdate 中?

【问题讨论】:

  • 不会将已经是 True 的值设置为 True 是空操作吗?为什么要取消?
  • @Blorgbeard,如果您指的是#1,我的意思是,但我不够明确。我更新了项目符号并添加了一个用例进行说明。

标签: c# .net multithreading concurrency concurrentdictionary


【解决方案1】:

如果我了解您想要实现的目标,我认为您不能为此使用 ConcurrentDictionary&lt;string, bool&gt;

一种可能的解决方案是拥有一个封装给定主机扫描的类:

public class Scanner
{
    private static _syncRoot = new object();

    public Scanner(string host)
    {
        Host = host;
        StartScanning();
    }

    public string Host {get; private set; }

    public bool IsScanning {get; private set; }

    public void StartScanning()
    {
        lock(_syncRoot)
        {
            if (!IsScanning)
            {
                IsScanning = true;
                // Start scanning Host asynchronously
                ...
            }
        }
    }

    private void EndScanning()
    {
        // Called when asynchronous scanning has completed
        IsScanning = false;
    }
}

然后是字典ConcurrentDictionary&lt;string, Lazy&lt;Scanner&gt;&gt;

您可以按如下方式使用它:

Scanner s = dictionary.GetOrAdd(host, new Lazy<Scanner>(() => new Scanner(host));
s.StartScanning();

Lazy&lt;Scanner&gt; 实例将使用默认的LazyThreadSafetyMode.ExecutionAndPublication 模式,这意味着只有一个线程会调用工厂委托来为给定主机实例化扫描器。

根据我对您的问题的理解,在我看来这就是您想要实现的目标,即不要多次扫描同一主机。

【讨论】:

    【解决方案2】:

    使用该博客文章中提到的 AddOrUpdate 方法。在您的 add delgate 中,将 bool 设置为 true。在您的更新委托中,让它检查作为参数传递给委托的布尔值并始终返回 true。我这么说是因为你在说

    • 如果为假,则设置为真
    • 如果为真,则取消更新(即保留为真)。所以你不妨设置为true

    如果还有其他条件缺失,请详细说明。

    【讨论】:

    • 我在我的问题中添加了更多信息......我如何与我的调用线程通信当前正在由对等线程进行的工作(布尔已经是 true)或者 AddOrUpdate 工作并且它是调用线程有机会做一些工作。
    【解决方案3】:

    你可以按照以下方式做一些事情:

    if (dic.TryAdd(domain, true)) || (dic.TryUpdate(domain, true, false)) {
       // this thread just added a new 'true' entry, 
       // or changed an existing 'false' entry to 'true'
    }
    

    当然,它会导致两倍的键查找。但我看不到在 ConcurrentDictionary 内完成所有事情的方法。

    【讨论】:

    • 从代码的角度来看,这更干净。您认为查找次数可能更少...这种方法或将bool 包装在一个也具有DateTime 的ThreadID 的对象中(以计算锁定的持续时间)...然后不更改@987654326 @。当然会有这里列出的并发更新stackoverflow.com/q/13445461/328397 ...
    【解决方案4】:

    尝试使用 ConcurrentDictionary>。

    当您创建 Lazy 时,传入一个在站点上运行扫描的委托。第一次访问您的 Lazy.Value 属性时,将运行扫描。任何后续调用者都将被阻止,直到第一次扫描完成。扫描完成后,访问 Lazy.Value 的任何人都将获得该值,但永远不会运行第二次扫描。

    【讨论】:

      【解决方案5】:

      ConcurrentDictionary 的并发性使得这不起作用。

      您真正必须对字典中已有的值采取行动的唯一机会是在updateValueFactory 中,但这项工作将发生在更新实际发生之前并且值设置为@ 987654323@。在此期间,另一个线程也可能尝试AddOrUpdate,在这种情况下,它仍然会看到false的旧值,并重新启动更新逻辑。

      这里有一个示例程序来证明这一点:

      using System;
      using System.Collections.Concurrent;
      using System.Threading.Tasks;
      
      namespace ConcurrentDictionaryCancelTest {
          class Program {
              static void Main( string[] args ) {
                  var example = new ConcurrentDictionary<string, bool>();
      
                  for( var i = 0; i < 3; i++ ) {
                      example.AddOrUpdate( i.ToString(), false, ( key, oldValue ) => false );
                  }
      
                  Parallel.For( 0, 8, x => {
                      example.AddOrUpdate(
                          ( x % 3 ).ToString(),
                          ( key ) => {
                              Console.WriteLine( "addValueFactory called for " + key );
                              return true;
                          },
                          ( key, oldValue ) => {
                              Console.WriteLine( "updateValueFactory called for " + key );
                              if( !oldValue ) {
                                  var guid = Guid.NewGuid();
                                  Console.WriteLine( 
                                      key + " is calling UpdateLogic: " + guid.ToString() 
                                  );
                                  UpdateLogic( key, guid );
                              }
                              return true;
                          }
                      );
                  } );
              }
      
              public static void UpdateLogic( string key, Guid guid ) {
                  Console.WriteLine( 
                      "UpdateLogic has been called for " + key + ": " + guid.ToString()
                  );
              }
          }
      }
      

      还有一些示例输出:

      updateValueFactory called for 0
      updateValueFactory called for 1
      updateValueFactory called for 2
      updateValueFactory called for 0
      updateValueFactory called for 1
      0 is calling UpdateLogic: cdd1b1dd-9d96-417d-aee7-4c4aec7fafbf
      1 is calling UpdateLogic: 161c5f35-a2d7-44bf-b881-e56ac713b340
      UpdateLogic has been called for 0: cdd1b1dd-9d96-417d-aee7-4c4aec7fafbf
      updateValueFactory called for 1
      1 is calling UpdateLogic: 6a032c22-e8d4-4016-a212-b09e41bf4d68
      UpdateLogic has been called for 1: 6a032c22-e8d4-4016-a212-b09e41bf4d68
      updateValueFactory called for 0
      updateValueFactory called for 2
      2 is calling UpdateLogic: 76c13581-cd55-4c88-961c-12c6d277ff00
      UpdateLogic has been called for 2: 76c13581-cd55-4c88-961c-12c6d277ff00
      1 is calling UpdateLogic: d71494b6-265f-4ec8-b077-af5670c02390
      UpdateLogic has been called for 1: d71494b6-265f-4ec8-b077-af5670c02390
      UpdateLogic has been called for 1: 161c5f35-a2d7-44bf-b881-e56ac713b340
      updateValueFactory called for 1
      updateValueFactory called for 1
      0 is calling UpdateLogic: f6aa3460-444b-41eb-afc6-3d6afa2f6512
      UpdateLogic has been called for 0: f6aa3460-444b-41eb-afc6-3d6afa2f6512
      2 is calling UpdateLogic: d911dbd1-7150-4823-937a-26abb446c669
      UpdateLogic has been called for 2: d911dbd1-7150-4823-937a-26abb446c669
      updateValueFactory called for 0
      updateValueFactory called for 2
      

      注意第一次调用 updateValueFactory 为 0 到要调用 UpdateLogic 到实际执行之间的延迟。在此期间,即在值更新为 true 之前,updateValueFactory 再次被调用为 0,这导致 UpdateLogic 也再次为 0 运行。

      您需要某种锁来确保读取值、调用更新逻辑和设置新值都是一个原子操作。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-09-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多