Parallel.ForEach loop 在C#中只允许一个线程访问DB
Parallel.ForEach loop Allow only one thread to access DB in C#
我有一段代码,其中多个线程使用来自 ConcurrentBag 类型字符串的共享 ID 属性 进行访问,如下所示:
var ids = new ConcurrentBag<string>();
// List contains lets say 10 ID's
var apiKey = ctx.ApiKey.FirstOrDefault();
Parallel.ForEach(ids, id =>
{
try
{
// Perform API calls
}
catch (Exception ex)
{
if (ex.Message == "Expired")
{
// the idea is that if that only one thread can access the DB record to update it, not multiple ones
using (var ctx = new MyEntities())
{
var findApi= ctx.ApiKeys.Find(apiKey.RecordId);
findApi.Expired = DateTime.Now.AddHours(1);
findApi.FailedCalls += 1;
}
}
}
});
所以在这种情况下,如果我有一个用于 API 调用的 10 个 ID 和 1 个密钥的列表,一旦密钥达到每小时调用限制,我将捕获异常API 然后标记下一个小时不使用的密钥。
但是,在我上面粘贴的代码中,所有 10 个线程都将从数据库访问记录并将失败的调用计为 10 次,而不是仅 1..:/
所以我的问题是如何防止所有线程更新数据库记录,而是只允许一个线程访问数据库,更新记录(通过 +1 添加失败的调用) ?
我怎样才能做到这一点?
看来只要出错就更新apiKey.RecordId一次,为什么不跟踪出错的事实,最后更新一次呢?例如
var ids = new ConcurrentBag<string>();
// List contains lets say 10 ID's
var apiKey = ctx.ApiKey.FirstOrDefault();
var expired = false;
Parallel.ForEach(ids, id =>
{
try
{
// Perform API calls
}
catch (Exception ex)
{
if (ex.Message == "Expired")
{
expired = true;
}
}
}
if (expired)
{
// the idea is that if that only one thread can access the DB record to
// update it, not multiple ones
using (var ctx = new MyEntities())
{
var findApi= ctx.ApiKeys.Find(apiKey.RecordId);
findApi.Expired = DateTime.Now.AddHours(1);
findApi.FailedCalls += 1;
}
});
您处于并行循环中,因此最有可能的行为是 10 个线程中的每一个都将被触发,尝试使用过期密钥连接到您的 API 然后全部失败,抛出异常。
对此有几个合理的解决方案:
使用前检查密钥
能否让循环中的第一个 运行 乱序?例如:
var ids = new ConcurrentBag<string>();
var apiKey = ctx.ApiKey.FirstOrDefault();
bool expired = true;
try {
// Perform API calls
expired = false;
}
catch(Exception ex) {
// log to database once
}
// Or grab another, newer key?
if (!expired)
{
Parallel.ForEach(ids.Skip(1), id =>
{
// Perform API Calls
}
}
如果密钥在您使用它之前可能已经过期,但在您使用它时将处于活动状态,那么这将工作得很好。
坚持失败
如果密钥在您开始时可能有效,但在您使用它时可能会过期,您可能想尝试捕获该失败,然后在结束时记录。
var ids = new ConcurrentBag<string>();
var apiKey = ctx.ApiKey.FirstOrDefault();
// Assume the key hasn't expired - don't set to false within the loops
bool expired = false;
Parallel.ForEach(ids.Skip(1), id =>
{
try {
// Perform API calls
}
catch (Exception e) {
if (e.Message == "Expired") {
// Doesn't matter if many threads set this to true.
expired = true;
}
}
if (expired) {
// Log to database once.
}
}
我有一段代码,其中多个线程使用来自 ConcurrentBag 类型字符串的共享 ID 属性 进行访问,如下所示:
var ids = new ConcurrentBag<string>();
// List contains lets say 10 ID's
var apiKey = ctx.ApiKey.FirstOrDefault();
Parallel.ForEach(ids, id =>
{
try
{
// Perform API calls
}
catch (Exception ex)
{
if (ex.Message == "Expired")
{
// the idea is that if that only one thread can access the DB record to update it, not multiple ones
using (var ctx = new MyEntities())
{
var findApi= ctx.ApiKeys.Find(apiKey.RecordId);
findApi.Expired = DateTime.Now.AddHours(1);
findApi.FailedCalls += 1;
}
}
}
});
所以在这种情况下,如果我有一个用于 API 调用的 10 个 ID 和 1 个密钥的列表,一旦密钥达到每小时调用限制,我将捕获异常API 然后标记下一个小时不使用的密钥。
但是,在我上面粘贴的代码中,所有 10 个线程都将从数据库访问记录并将失败的调用计为 10 次,而不是仅 1..:/
所以我的问题是如何防止所有线程更新数据库记录,而是只允许一个线程访问数据库,更新记录(通过 +1 添加失败的调用) ?
我怎样才能做到这一点?
看来只要出错就更新apiKey.RecordId一次,为什么不跟踪出错的事实,最后更新一次呢?例如
var ids = new ConcurrentBag<string>();
// List contains lets say 10 ID's
var apiKey = ctx.ApiKey.FirstOrDefault();
var expired = false;
Parallel.ForEach(ids, id =>
{
try
{
// Perform API calls
}
catch (Exception ex)
{
if (ex.Message == "Expired")
{
expired = true;
}
}
}
if (expired)
{
// the idea is that if that only one thread can access the DB record to
// update it, not multiple ones
using (var ctx = new MyEntities())
{
var findApi= ctx.ApiKeys.Find(apiKey.RecordId);
findApi.Expired = DateTime.Now.AddHours(1);
findApi.FailedCalls += 1;
}
});
您处于并行循环中,因此最有可能的行为是 10 个线程中的每一个都将被触发,尝试使用过期密钥连接到您的 API 然后全部失败,抛出异常。
对此有几个合理的解决方案:
使用前检查密钥
能否让循环中的第一个 运行 乱序?例如:
var ids = new ConcurrentBag<string>();
var apiKey = ctx.ApiKey.FirstOrDefault();
bool expired = true;
try {
// Perform API calls
expired = false;
}
catch(Exception ex) {
// log to database once
}
// Or grab another, newer key?
if (!expired)
{
Parallel.ForEach(ids.Skip(1), id =>
{
// Perform API Calls
}
}
如果密钥在您使用它之前可能已经过期,但在您使用它时将处于活动状态,那么这将工作得很好。
坚持失败
如果密钥在您开始时可能有效,但在您使用它时可能会过期,您可能想尝试捕获该失败,然后在结束时记录。
var ids = new ConcurrentBag<string>();
var apiKey = ctx.ApiKey.FirstOrDefault();
// Assume the key hasn't expired - don't set to false within the loops
bool expired = false;
Parallel.ForEach(ids.Skip(1), id =>
{
try {
// Perform API calls
}
catch (Exception e) {
if (e.Message == "Expired") {
// Doesn't matter if many threads set this to true.
expired = true;
}
}
if (expired) {
// Log to database once.
}
}