我应该如何在ConcurrentDictionary中“取消”AddOrUpdate?

时间:2012-11-18 23:00:59

标签: c# .net multithreading concurrency concurrentdictionary

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

对于ConcurrentDictionary<string,bool>

  1. 如果字符串不存在,请添加它,并确保在添加
  2. 时将bool设置为True
  3. 如果该字符串确实存在,则只有在错误时才将bool更改为True。否则取消更新
  4. 我的用例

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

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

    请记住......

    将独立于.NET4的TPL中的许多独立线程调用AddOrUpdate。每个线程需要决定是否需要处理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?

5 个答案:

答案 0 :(得分:4)

如果我理解您要实现的目标,我认为您不能使用ConcurrentDictionary<string, bool>

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

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<string, Lazy<Scanner>>

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

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

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

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

答案 1 :(得分:1)

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

  • 如果为false,则将其设置为true
  • 如果是,请取消更新(即保持为真)。所以你不妨把它设置为真

如果还有其他一些情况缺失请详细说明。

答案 2 :(得分:1)

您可以采取以下措施:

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内部完成整个事情。

答案 3 :(得分:0)

尝试使用ConcurrentDictionary&gt;。

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

答案 4 :(得分:0)

ConcurrentDictionary的并发性使其无效。

您真正必须对字典中已有的值采取行动的唯一机会是在updateValueFactory,但是在实际发生更新并且设置了值之前,该工作将发生true。在此期间,另一个线程也可能尝试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运行。

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