集合被修改;枚举操作可能无法执行(带锁的多线程)
Collection was modified; enumeration operation may not execute (Multi Threading with Locks)
所以我知道这个问题以前在这里被问过,但是这里的情况有点不同。
我有一个生成工作线程的服务应用程序。主服务线程组织如下:
public void PollCrunchFilesTask()
{
try
{
var stuckDeletedServTableFiles = MaintenanceDbContext.stuckDeletedServTableFiles;
var stuckErrorStatusFiles = MaintenanceDbContext.stuckErrorStatusFiles;
while (_signalPollAutoEvent.WaitOne())
{
try
{
Poll();
lock (stuckDelLock)
{
if(stuckDeletedServTableFiles.Count > 0)
MaintenanceDbContext.DeleteFilesToBeDeletedInServiceTable(stuckDeletedServTableFiles);
}
lock (errorStatusLock)
{
if (stuckErrorStatusFiles.Count > 0)
MaintenanceDbContext.UpdateStuckErrorServiceLogEntries(stuckErrorStatusFiles);
}
}
catch (Exception ex)
{
}
}
}
catch (Exception ex)
{
}
}
Inside Poll 你有这样的逻辑:
public void Poll()
{
try
{
if (ProducerConsumerQueue.Count() == 0 && ThreadCount_Diff_ActiveTasks > 0)
{
var dequeuedItems = MetadataDbContext.UpdateOdfsServiceEntriesForProcessingOnPollInterval(ThreadCount_Diff_ActiveTasks);
var handlers = Producer.GetParserHandlers(dequeuedItems);
foreach (var handler in handlers)
{
ProducerConsumerQueue.EnqueueTask(handler.Execute, CancellationTokenSource.Token);
}
}
}
catch (Exception ex)
{
}
}
那个ProducerConsumerQueue.EnqueueTask(handler.Execute,CancellationTokenSource.Token);启动 4 个工作线程,在这些线程中的任何一个中,随时调用以下函数:
public static int DeleteServiceEntry(string logFileName)
{
int rowsAffected = 0;
var stuckDeletedServTableFiles = MaintenanceDbContext.stuckDeletedServTableFiles;
try
{
string connectionString = GetConnectionString();
throw new Exception($"Testing Del HashSet");
using (SqlConnection connection = new SqlConnection())
{
//Attempt some query
}
}
catch (Exception ex)
{
lock (stuckDelLock)
{
stuckDeletedServTableFiles.Add(logFileName);
}
}
return rowsAffected;
}
现在我正在测试 stuckDeletedServTableFiles 哈希集,它仅在查询过程中出现异常时调用。这就是我故意抛出异常的原因。该哈希集是在函数 DeleteFilesToBeDeletedInServiceTable() 中的主服务线程上操作的哈希集;谁的摘录定义如下:
public static int DeleteFilesToBeDeletedInServiceTable(HashSet<string> stuckDeletedServTableFiles)
{
int rowsAffected = 0;
string logname = String.Empty; //used to collect error log
var removedHashset = new HashSet<string>();
try
{
var dbConnString = MetadataDbContext.GetConnectionString();
string serviceTable = Constants.SERVICE_LOG_TBL;
using (SqlConnection connection = new SqlConnection(dbConnString))
{
SqlCommand cmd = new SqlCommand();
cmd.CommandType = CommandType.Text;
cmd.CommandText = $"DELETE FROM {serviceTable} WHERE LOGNAME = @LOGNAME";
cmd.Parameters.Add("@LOGNAME", SqlDbType.NVarChar);
cmd.Connection = connection;
connection.Open();
foreach (var logFname in stuckDeletedServTableFiles)
{
cmd.Parameters["@LOGNAME"].Value = logFname;
logname = logFname;
int currRowsAffected = cmd.ExecuteNonQuery();
rowsAffected += currRowsAffected;
if (currRowsAffected == 1)
{
removedHashset.Add(logFname);
Logger.Info($"Removed Stuck {logFname} Marked for Deletion from {serviceTable}");
}
}
Logger.Info($"Removed {rowsAffected} stuck files Marked for Deletion from {serviceTable}");
}
stuckDeletedServTableFiles.ExceptWith(removedHashset);
}
catch (Exception ex)
{
}
return rowsAffected;
}
考虑到 hashset stuckDeletedServTableFiles 可以被包括主服务线程在内的多个线程同时访问,我在 DeleteFilesToBeDeletedInServiceTable() 和函数 DeleteServiceEntry 操作之前锁定了主服务线程().我是 C# 的新手,但我认为这就足够了吗?我假设因为在函数 DeleteFilesToBeDeletedInServiceTable() 的主服务线程上调用了一个锁,所以该锁会阻止任何东西使用哈希集,因为它是由函数操作的。为什么会出现此错误?
注意我没有修改 forloop 中的 Hashset。我只在循环完成后才这样做。我在遍历哈希集时遇到此错误。我想是因为另一个线程正试图修改它。那么问题是,当我在服务级别上锁定调用它的函数时,为什么线程能够修改 hashSet?
现在,我通过用锁包围 DeleteFilesToBeDeletedInServiceTable() 中的 for 循环并在语句 stuckDeletedServTableFiles.ExceptWith(removedHashset) 上调用相同的锁来修复它;在那个函数里面。我不确定这是否有成本,但它似乎会起作用。并给出在实践中这个问题发生的频率有多低,我想它不会花费太多。特别是自从那个函数被调用的时候,我们就没有做密集的事情。到那时文件处理已经完成,主调用者正在使用另一个线程
所以我知道这个问题以前在这里被问过,但是这里的情况有点不同。
我有一个生成工作线程的服务应用程序。主服务线程组织如下:
public void PollCrunchFilesTask()
{
try
{
var stuckDeletedServTableFiles = MaintenanceDbContext.stuckDeletedServTableFiles;
var stuckErrorStatusFiles = MaintenanceDbContext.stuckErrorStatusFiles;
while (_signalPollAutoEvent.WaitOne())
{
try
{
Poll();
lock (stuckDelLock)
{
if(stuckDeletedServTableFiles.Count > 0)
MaintenanceDbContext.DeleteFilesToBeDeletedInServiceTable(stuckDeletedServTableFiles);
}
lock (errorStatusLock)
{
if (stuckErrorStatusFiles.Count > 0)
MaintenanceDbContext.UpdateStuckErrorServiceLogEntries(stuckErrorStatusFiles);
}
}
catch (Exception ex)
{
}
}
}
catch (Exception ex)
{
}
}
Inside Poll 你有这样的逻辑:
public void Poll()
{
try
{
if (ProducerConsumerQueue.Count() == 0 && ThreadCount_Diff_ActiveTasks > 0)
{
var dequeuedItems = MetadataDbContext.UpdateOdfsServiceEntriesForProcessingOnPollInterval(ThreadCount_Diff_ActiveTasks);
var handlers = Producer.GetParserHandlers(dequeuedItems);
foreach (var handler in handlers)
{
ProducerConsumerQueue.EnqueueTask(handler.Execute, CancellationTokenSource.Token);
}
}
}
catch (Exception ex)
{
}
}
那个ProducerConsumerQueue.EnqueueTask(handler.Execute,CancellationTokenSource.Token);启动 4 个工作线程,在这些线程中的任何一个中,随时调用以下函数:
public static int DeleteServiceEntry(string logFileName)
{
int rowsAffected = 0;
var stuckDeletedServTableFiles = MaintenanceDbContext.stuckDeletedServTableFiles;
try
{
string connectionString = GetConnectionString();
throw new Exception($"Testing Del HashSet");
using (SqlConnection connection = new SqlConnection())
{
//Attempt some query
}
}
catch (Exception ex)
{
lock (stuckDelLock)
{
stuckDeletedServTableFiles.Add(logFileName);
}
}
return rowsAffected;
}
现在我正在测试 stuckDeletedServTableFiles 哈希集,它仅在查询过程中出现异常时调用。这就是我故意抛出异常的原因。该哈希集是在函数 DeleteFilesToBeDeletedInServiceTable() 中的主服务线程上操作的哈希集;谁的摘录定义如下:
public static int DeleteFilesToBeDeletedInServiceTable(HashSet<string> stuckDeletedServTableFiles)
{
int rowsAffected = 0;
string logname = String.Empty; //used to collect error log
var removedHashset = new HashSet<string>();
try
{
var dbConnString = MetadataDbContext.GetConnectionString();
string serviceTable = Constants.SERVICE_LOG_TBL;
using (SqlConnection connection = new SqlConnection(dbConnString))
{
SqlCommand cmd = new SqlCommand();
cmd.CommandType = CommandType.Text;
cmd.CommandText = $"DELETE FROM {serviceTable} WHERE LOGNAME = @LOGNAME";
cmd.Parameters.Add("@LOGNAME", SqlDbType.NVarChar);
cmd.Connection = connection;
connection.Open();
foreach (var logFname in stuckDeletedServTableFiles)
{
cmd.Parameters["@LOGNAME"].Value = logFname;
logname = logFname;
int currRowsAffected = cmd.ExecuteNonQuery();
rowsAffected += currRowsAffected;
if (currRowsAffected == 1)
{
removedHashset.Add(logFname);
Logger.Info($"Removed Stuck {logFname} Marked for Deletion from {serviceTable}");
}
}
Logger.Info($"Removed {rowsAffected} stuck files Marked for Deletion from {serviceTable}");
}
stuckDeletedServTableFiles.ExceptWith(removedHashset);
}
catch (Exception ex)
{
}
return rowsAffected;
}
考虑到 hashset stuckDeletedServTableFiles 可以被包括主服务线程在内的多个线程同时访问,我在 DeleteFilesToBeDeletedInServiceTable() 和函数 DeleteServiceEntry 操作之前锁定了主服务线程().我是 C# 的新手,但我认为这就足够了吗?我假设因为在函数 DeleteFilesToBeDeletedInServiceTable() 的主服务线程上调用了一个锁,所以该锁会阻止任何东西使用哈希集,因为它是由函数操作的。为什么会出现此错误?
注意我没有修改 forloop 中的 Hashset。我只在循环完成后才这样做。我在遍历哈希集时遇到此错误。我想是因为另一个线程正试图修改它。那么问题是,当我在服务级别上锁定调用它的函数时,为什么线程能够修改 hashSet?
现在,我通过用锁包围 DeleteFilesToBeDeletedInServiceTable() 中的 for 循环并在语句 stuckDeletedServTableFiles.ExceptWith(removedHashset) 上调用相同的锁来修复它;在那个函数里面。我不确定这是否有成本,但它似乎会起作用。并给出在实践中这个问题发生的频率有多低,我想它不会花费太多。特别是自从那个函数被调用的时候,我们就没有做密集的事情。到那时文件处理已经完成,主调用者正在使用另一个线程