C# “我该怎么做?”;取消“;ConcurrentDictionary中的AddOrUpdate?

C# “我该怎么做?”;取消“;ConcurrentDictionary中的AddOrUpdate?,c#,.net,multithreading,concurrency,concurrentdictionary,C#,.net,Multithreading,Concurrency,Concurrentdictionary,我已经阅读了MSDN文档,需要以下逻辑: 对于ConcurrentDictionary 如果字符串不存在,请添加它,并确保在添加时将bool设置为True 如果字符串确实存在,则仅当布尔值为false时才将其更改为True。否则取消更新 我的用例 我有几个DNS域来扫描恶意软件。在我实时检索的列表中,很可能会有重复项。我收到的DNS域名列表批量为100个或更少,将有超过10000个域名进行扫描 我只想在10000个域的每次迭代中扫描一次DNS主机。Abool==true表示当前正在扫描它,我应该

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

对于
ConcurrentDictionary

  • 如果字符串不存在,请添加它,并确保在添加时将bool设置为
    True
  • 如果字符串确实存在,则仅当布尔值为false时才将其更改为
    True
    。否则取消更新
  • 我的用例

    我有几个DNS域来扫描恶意软件。在我实时检索的列表中,很可能会有重复项。我收到的DNS域名列表批量为100个或更少,将有超过10000个域名进行扫描

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

    记住……

    AddOrUpdate将独立于.NET4的TPL中的许多独立线程进行调用。每个线程需要决定是否需要处理字典的
    键中提到的值。。。或者继续下一个。只有一把“钥匙”应该对它进行操作

    我需要向调用线程发出更新成功或失败的信号。此外,AddOrUpdate的函数似乎会被多次调用。我想这可能意味着我的调用线程将被混淆,无法取消
    键上的工作或继续它。(请记住,只有一个线程可以在
    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();
            });
    });
    
    问题


    如何将此“取消更新”功能添加到AddOrUpdate?

    使用该博客文章中提到的AddOrUpdate方法。在AddDelgate中,将bool设置为true。在update委托中,让它检查作为参数传递给委托的bool值,并始终返回true。我这样说是因为

    • 如果为false,则将其设置为true
    • 如果是真的,取消更新(即保持为真)。因此,您不妨将其设置为真

    如果缺少某些其他条件,请详细说明。

    您可以按照以下方法进行操作:

    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

    中完成整个任务的方法。如果我理解您试图实现的目标,我认为您不能使用
    ConcurrentDictionary

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

    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

    您将按如下方式使用它:

    Scanner s = dictionary.GetOrAdd(host, new Lazy<Scanner>(() => new Scanner(host));
    s.StartScanning();
    
    Scanner s=dictionary.GetOrAdd(主机,新延迟(()=>newscanner(主机));
    s、 开始扫描();
    
    Lazy
    实例将使用默认的
    LazyThreadSafetyMode.ExecutionAndPublication
    模式,这意味着只有一个线程会调用工厂委托来实例化给定主机的扫描程序


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

    尝试使用ConcurrentDictionary>


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

    ConcurrentDictionary
    的并发性使其无法工作

    您真正需要对字典中已有的值执行操作的唯一机会是
    updateValueFactory
    ,但该工作将在更新实际发生之前进行,并且该值设置为
    true
    。在此期间,另一个线程也可能尝试
    添加或更新
    ,在这种情况下,它仍会看到将旧值设置为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
    时(当调用
    UpdateLogic
    时)与实际执行时之间的延迟。在此期间,即在值更新为
    true
    之前,
    updateValueFactory
    再次调用0,这将导致
    UpdateLogic也将再次为0运行


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

    将一个已经为
    True
    的值设置为
    True
    不是一个不可操作的操作吗?为什么要取消呢?@Blorgbeard,如果你指的是#1,我的意思是,但我不够明确。我更新了项目符号并添加了一个用于澄清的用例。我在问题中添加了更多信息…我如何与调用线程通信,说明当前正在由对等线程进行工作(bool已经
    为true
    )或者AddOrUpdate起作用了,这是调用线程做一些工作的机会。从代码的角度来看,这更干净。您认为查找次数可能会更少……这种方法或将
    bool
    包装在一个对象中,该对象也具有ThreadID和
    DateTime
    (用于计算锁的持续时间)…然后不更改
    。当然,这里列出的同步更新。。。
    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