如何以线程安全的方式在.NET ConcurrentDictionary上调用GetOrAdd方法?

时间:2016-10-18 01:04:53

标签: c# .net multithreading collections thread-safety

我试图拥有keys的集合......如果密钥不存在,那么我需要DoSomeMethod()然后将密钥添加到集合中。

问题是,这需要能够同时处理多个尝试添加相同密钥的线程。

如果两个线程具有相同的密钥,则只有一个线程将执行DoSomeMethod()而另一个线程需要等待。

我已经考虑过使用ConcurrentDictionaryGetOrAddwith the Func(..) param option)方法了,但似乎两者都是“关闭”。如果两个线程具有相同的密钥,则同时进行。我认为GetOrAdd的实现将是

  • 锁定'键'
  • value获取key
  • 如果没有value则执行任何操作..现在设置值。
  • 返回value ......以及任何其他key点击将等到锁完成。

感觉就像GetOrAdd方法所调用的自定义方法一样,线程安全。

MSDN文档也提出了这个建议吗?

  

备注
  如果您在不同的线程上同时调用GetOrAdd,可能会多次调用addValueFactory,但每次调用时其键/值对可能不会添加到字典中

Contrite示例:我将文件从source复制到destination

  • 复制source文件时,如果我们尝试检查并创建目标文件夹,请检查该集合。
  • 如果集合没有key,那么检查目标文件夹是否不存在......如果它不存在则创建它。
  • 创建目标文件夹后,将此文件夹名称/路径存储在集合中。
  • 重复所有文件。

因此,如果我们还没有完成目标文件夹,我们只会创建目标文件夹。

我想要锁定集合KEY ....

2 个答案:

答案 0 :(得分:3)

<table>
  <thead>
    <tr>
      <th>Col 1</th>
      <th>Col 2</th>
      <th>Col 3</th>
      <th>Col 4</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Row 1</td>
      <td>Row 1</td>
      <td>Row 1</td>
      <td>Row 1</td>
    </tr>

    ....

  </tbody>
</table>

我想我应该稍微描述一下。基本上这里发生的是,如果两个调用者同时在public class OnceOnlyConcurrent<TKey, TValue> { private readonly ConcurrentDictionary<TKey, Lazy<TValue>> _dictionary = new ConcurrentDictionary<TKey, Lazy<TValue>>(); public TValue GetOrAdd(TKey key, Func<TValue> computation) { var result = _dictionary.AddOrUpdate(key, _ => new Lazy<TValue>(computation, LazyThreadSafetyMode.ExecutionAndPublication), (_, v) => v); return result.Value; } } 上发生,AddOrUpdate将始终调用addValueFactory代理两次,这两个调用除了返回之外都不会执行任何操作包含计算的AddOrUpdate引用。

Lazy<T>内,两个结果都会被捕获,但会删除一个。只有AddOrUpdate的一个实例将返回给Lazy<T>的两个调用者,因此一个AddOrUpdate将控制被调用的计算。

然后,在下一行,当我们要求Lazy<T>时,这实际上将触发此自定义.Value的一个调用者的计算,而另一个将在第一个计算时阻塞 - 这是GetOrAddLazy<T>)的第二个参数的功能。顺便说一下,这是LazyThreadSafteMode.ExecutionAndPublication的默认行为,所以你真的不需要第二个参数 - 我在这篇文章中用它来更清楚。

当然,这段代码也可以作为扩展方法编写,但不幸的是,您必须知道在里面创建一个包含Lazy<T>个对象的字典,所以我认为它更适合作为{Lazy<T>的包装类。 {1}}。

答案 1 :(得分:-1)

不需要GetOrAdd。对路径和密钥存在的简单检查就足够了:

class FileWorker
{
    private object _sync;
    private IDictionary<string, Task> _destTasks;

    public FileWorker()
    {
        _sync = new object();
        _destTasks = new Dictionary<string, Task>();
    }

    public async Task Copy(IEnumerable<FileInfo> files, string destinationFolder)
    {
        await Task.WhenAll(files.Select(f => Copy(f, destinationFolder)));
    }

    private async Task CreateDestination(string path)
    {
        await Task.Run(() =>
        {
            if (!Directory.Exists(path))
            {
                Directory.CreateDirectory(path);
            }
        });
    }

    private Task Destination(string path)
    {
        lock(_sync)
        {
            if (!_destTasks.ContainsKey(path))
            {
                _destTasks[path] = CreateDestination(path);
            }
        }
        return _destTasks[path];
    }

    private async Task Copy(FileInfo file, string destinationFolder)
    {
        await Destination(destinationFolder).ContinueWith(task => file.CopyTo(Path.Combine(destinationFolder, file.Name), true));
    }
}

class Program
{
    static void Main(string[] args)
    {
        var file1 = new FileInfo("file1.tmp");
        using(var writer = file1.CreateText())
        {
            writer.WriteLine("file 1");
        }
        var file2 = new FileInfo("file2.tmp");
        using(var writer = file2.CreateText())
        {
            writer.WriteLine("file 2");
        }
        var worker = new FileWorker();
        worker.Copy(new[] { file1, file2 }, @"C:\temp").Wait();
        Console.ReadLine();
    }
}