C#Concurent字典 - 锁定值

时间:2017-07-14 11:37:32

标签: c# .net thread-safety concurrentdictionary

我正在开发一项服务,负责记录发送到我们服务的请求。该服务正在脱机工作(被解雇并忘记)。 我们根据一些输入参数(产品ID)将请求保存到不同的数据库。我们不希望每次有人提出请求时都保存到数据库 - 我们宁愿建立一些“批处理”来插入并每InsertManyN执行ConcurrentDictionary(比方说10)秒)。我已经开始实现这一点,现在我正在努力解决两件事:

  1. 我需要使用ConcurrentDictionary吗?看起来我会用普通词典实现相同的
  2. 如果上面的回答是“不,在你的情况下,ConcurrentDictionary没有任何好处” - 是否有办法重新编写我的代码以“正确”使用AddOrUpdate,这样我就可以避免使用锁定并确保 // dictionary where key is ProductId and value is a list of items to insert to that product database ConcurrentDictionary<string, List<QuoteDetails>> _productDetails; public SaverService(StatelessServiceContext context) : base(context) { _productDetails = new ConcurrentDictionary<string, List<QuoteDetails>>(); } // this function will be fired and forgotten by the external service public async Task SaveRecentRequest(RequestOptions requestData, Response responseData) { await Task.Run(() => { foreach (var token in requestData.ProductAccessTokens) { // this function will extract the specific product request ( one request can contain multiple products ) var details = SplitQuoteByProduct(requestData, responseData, token); _productDetails.AddOrUpdate(token, new List<QuoteDetails>() { details }, (productId, list) => { list.Add(details); return list; }); } }); } // this function will be executed by a timer every N amount of time public void SaveRequestsToDatabase() { lock (_productDetails) { foreach (var item in _productDetails) { // copy curent items and start a task which will process them SaveProductRequests(item.Key, item.Value.ToList()); // clear curent items item.Value.Clear(); } } } public async Task SaveProductRequests(string productId, List<QuoteDetails> productRequests) { // save received items to database /// ... } 与清除批次不会发生“冲突”?
  3. 让我粘贴代码段并进一步解释:

    SaveRequestsToDatabase

    我主要担心的是,如果没有锁定后续情况:

    1. item.Value.Clear();被解雇 - 并开始处理数据
    2. SaveRequestsToDatabase函数中调用SaveRecentRequest之前,外部服务会触发另一个AddOrUpdate函数,该函数使用相同的密钥执行SaveRequestsToDatabase - 这将向集合添加请求< / LI>
    3. UIImage *image = [UIImage imageNamed:@"iTunesArtwork1024.png"]; 正在完成并因此清除集合 - 但最初由2.添加的对象不在集合中,因此未处理

3 个答案:

答案 0 :(得分:3)

通常,并发问题来自于首先没有选择正确的数据结构。

在您的情况下,您有两个工作流程:

  • n生产者,同时和连续地排队事件
  • 1个消费者,在给定时间出列并处理事件

您的问题是,即使不需要,您也可以立即尝试对事件进行分类。将事件保存为并发部分中的简单流,并仅在消费者部分对它们进行排序,因为那里没有并发性。

ConcurrentQueue<(string token, QuoteDetails details)> _productDetails;

public SaverService(StatelessServiceContext context)
    : base(context)
{
    _productDetails = new ConcurrentQueue<(string, QuoteDetails)>();
}

// this function will be fired and forgotten by the external service
public async Task SaveRecentRequest(RequestOptions requestData, Response responseData)
{
    await Task.Run(() => {
        foreach (var token in requestData.ProductAccessTokens)
        {
            // this function will extract the specific product request ( one request can contain multiple products )
            var details = SplitQuoteByProduct(requestData, responseData, token);
            _productDetails.Enqueue((token, details));
        }
    });
}

// this function will be executed by a timer every N amount of time
public void SaveRequestsToDatabase()
{
    var products = new List<(string token, QuoteDetails details)>();

    while (_productDetails.TryDequeue(out var item))
    {
        products.Add(item);
    }

    foreach (var group in products.GroupBy(i => i.token, i => i.Details))
    {
        SaveProductRequests(group.Key, group);
    }
}

public async Task SaveProductRequests(string productId, IEnumerable<QuoteDetails> productRequests)
{
    // save received items to database
    /// ...
}

答案 1 :(得分:2)

ConcurrentDictionary<string, List<QuoteDetails>> _productDetails;

不是线程安全的,因为List不是线程安全的。当一个线程正在向列表中添加条目时,另一个线程可能正在迭代它。这最终会失败。

我建议使用:

ConcurrentDictionary<string, ConcurrentQueue<QuoteDetails>> _productDetails;

或:

ConcurrentDictionary<string, BlockingCollection<QuoteDetails>> _productDetails;

您也可能完全删除ConcurrentDictionary

答案 2 :(得分:1)

每当您添加/删除/读取字典时,都需要锁定字典。即使在您忙于处理项目时,您当前的代码也允许SaveRecentRequest将项目添加到字典中。我建议采用以下方法

// dictionary where key is ProductId and value is a list of items to insert to that product database
Dictionary<string, List<QuoteDetails>> _productDetails;
public SaverService(StatelessServiceContext context)
    : base(context)
{
    _productDetails = new Dictionary<string, List<QuoteDetails>>();
}

// this function will be fired and forgotten by the external service
public async Task SaveRecentRequest(RequestOptions requestData, Response responseData)
{
    await Task.Run(() => {
        foreach (var token in requestData.ProductAccessTokens)
        {
            // this function will extract the specific product request ( one request can contain multiple products )
            var details = SplitQuoteByProduct(requestData, responseData, token);
            lock(_padlock)
            {
                _productDetails.AddOrUpdate(token, new List<QuoteDetails>() { details }, (productId, list) =>
                {
                    list.Add(details);
                    return list;
                });
            }
        }
    });
}

// this function will be executed by a timer every N amount of time
public void SaveRequestsToDatabase()
{
    Dictionary<string, List<QuoteDetails>> offboardingDictionary;
    lock (_padlock)
    {
        offboardingDictionary = _productDetails;
        _productDetails = new  Dictionary<string, List<QuoteDetails>>();
    }

    foreach (var item in offboardingDictionary)
    {

        // copy curent items and start a task which will process them
        SaveProductRequests(item.Key, item.Value.ToList());
        // clear curent items
        item.Value.Clear();
    }
}

public async Task SaveProductRequests(string productId, List<QuoteDetails> productRequests)
{
    // save received items to database
    /// ...
}

private readonly object _padlock = new object();

使用此功能可以在向字典中添加项目时锁定。为了提高保存性能,我们添加了对字典的新引用,然后用新实例替换原始字典。通过这种方式,我们最大限度地减少了锁定的时间,因此新的项目可以保存在新的字典中,而我们的保存线程会将项目从上一个字典卸载到数据库中。

我不认为你需要一个并发字典来完成这项任务,只要你锁定你的访问权限,普通字典就会这样做