集合被修改;枚举操作可能无法执行(带锁的多线程)

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) 上调用相同的锁来修复它;在那个函数里面。我不确定这是否有成本,但它似乎会起作用。并给出在实践中这个问题发生的频率有多低,我想它不会花费太多。特别是自从那个函数被调用的时候,我们就没有做密集的事情。到那时文件处理已经完成,主调用者​​正在使用另一个线程