我正在开发一项服务,负责记录发送到我们服务的请求。该服务正在脱机工作(被解雇并忘记)。
我们根据一些输入参数(产品ID)将请求保存到不同的数据库。我们不希望每次有人提出请求时都保存到数据库 - 我们宁愿建立一些“批处理”来插入并每InsertMany
个N
执行ConcurrentDictionary
(比方说10)秒)。我已经开始实现这一点,现在我正在努力解决两件事:
ConcurrentDictionary
吗?看起来我会用普通词典实现相同的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
/// ...
}
与清除批次不会发生“冲突”?让我粘贴代码段并进一步解释:
SaveRequestsToDatabase
我主要担心的是,如果没有锁定后续情况:
item.Value.Clear();
被解雇 - 并开始处理数据SaveRequestsToDatabase
函数中调用SaveRecentRequest
之前,外部服务会触发另一个AddOrUpdate
函数,该函数使用相同的密钥执行SaveRequestsToDatabase
- 这将向集合添加请求< / LI>
UIImage *image = [UIImage imageNamed:@"iTunesArtwork1024.png"];
正在完成并因此清除集合 - 但最初由2.添加的对象不在集合中,因此未处理答案 0 :(得分:3)
通常,并发问题来自于首先没有选择正确的数据结构。
在您的情况下,您有两个工作流程:
您的问题是,即使不需要,您也可以立即尝试对事件进行分类。将事件保存为并发部分中的简单流,并仅在消费者部分对它们进行排序,因为那里没有并发性。
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();
使用此功能可以在向字典中添加项目时锁定。为了提高保存性能,我们添加了对字典的新引用,然后用新实例替换原始字典。通过这种方式,我们最大限度地减少了锁定的时间,因此新的项目可以保存在新的字典中,而我们的保存线程会将项目从上一个字典卸载到数据库中。
我不认为你需要一个并发字典来完成这项任务,只要你锁定你的访问权限,普通字典就会这样做