C# WinForm多用户同时触发按钮致无限循环失效问题求助
SQL并发控制失效的解决办法
问题描述
在C# WinForm项目中,通过SQL字段(0/1切换)实现任务X的并发控制:当字段为0时执行流程,为1时进入等待循环,防止多人同时处理任务。但多个用户同时触发操作时,所有用户都会在某一用户将字段更新为1前读到0值,导致并发控制失效。
原代码示例:
SqlDataReader VerifyIfBusyReader = null; SqlCommand VerifyIfBusy = new SqlCommand("SELECT [Value] FROM [Variables] WHERE [TYPE] = 'IsBusy'", CONNECTION); int verify = 1; int counts = 0; int milliseconds = 2000; while (verify == 1) { counts++; if (counts == 30) { break; } VerifyIfBusyReader = VerifyIfBusy.ExecuteReader(); VerifyIfBusyReader.Read(); if (int.Parse(VerifyIfBusyReader[0].ToString()) == 1) { VerifyIfBusyReader.Close(); } else if (int.Parse(VerifyIfBusyReader[0].ToString()) == 0) { verify = 0; VerifyIfBusyReader.Close(); } Thread.Sleep(milliseconds); } SqlCommand IsBusyNow = new SqlCommand("UPDATE [Variables] SET [VALUE] = 1 WHERE [TYPE] = 'IsBusy'", CONNECTION); VerifyIfBusyReader = IsBusyNow.ExecuteReader(); VerifyIfBusyReader.Close();
注:流程结束后已实现将字段改回0的逻辑,无问题。
问题根源
原代码的查询-更新操作是非原子的:多个用户可以同时执行SELECT读到0值,之后又都执行UPDATE将字段设为1,完全绕过了并发控制逻辑。必须让“检查字段值+更新字段值”成为不可分割的原子操作,才能避免这种竞态条件。
解决办法
1. 原子化UPDATE操作(推荐)
将检查与更新合并为一条SQL语句,利用数据库的行锁机制保证原子性。执行UPDATE时仅在字段为0的情况下修改,然后通过ExecuteNonQuery()返回的影响行数判断是否成功抢到“锁”:
int counts = 0; int milliseconds = 2000; bool acquiredLock = false; while (!acquiredLock && counts < 30) { counts++; // 原子操作:仅当Value为0时更新为1 SqlCommand acquireLockCmd = new SqlCommand(@" UPDATE [Variables] SET [VALUE] = 1 WHERE [TYPE] = 'IsBusy' AND [Value] = 0", CONNECTION); int affectedRows = acquireLockCmd.ExecuteNonQuery(); if (affectedRows > 0) { // 成功抢到锁,退出循环执行任务 acquiredLock = true; } else { // 未抢到锁,等待后重试 Thread.Sleep(milliseconds); } } if (acquiredLock) { // 执行任务X的核心逻辑 } else { // 超时未抢到锁,提示用户“当前有其他用户在处理,请稍后再试” }
2. 事务+行级锁
开启数据库事务,查询时对目标行加更新锁,确保其他事务无法读取该行的旧值,再检查并更新字段,保证整个过程的原子性:
bool acquiredLock = false; int counts = 0; int milliseconds = 2000; while (!acquiredLock && counts < 30) { counts++; using (SqlTransaction transaction = CONNECTION.BeginTransaction()) { try { // 查询时加更新锁,阻止其他事务读取该行 SqlCommand checkCmd = new SqlCommand(@" SELECT [Value] FROM [Variables] WITH (UPDLOCK) WHERE [TYPE] = 'IsBusy'", CONNECTION, transaction); int currentValue = (int)checkCmd.ExecuteScalar(); if (currentValue == 0) { // 更新字段为1 SqlCommand updateCmd = new SqlCommand(@" UPDATE [Variables] SET [VALUE] = 1 WHERE [TYPE] = 'IsBusy'", CONNECTION, transaction); updateCmd.ExecuteNonQuery(); // 提交事务,确认锁生效 transaction.Commit(); acquiredLock = true; } else { // 释放事务,等待重试 transaction.Rollback(); Thread.Sleep(milliseconds); } } catch (Exception ex) { transaction.Rollback(); // 记录异常日志,等待后重试 Thread.Sleep(milliseconds); } } } if (acquiredLock) { // 执行任务X的核心逻辑 } else { // 超时提示用户 }
关键说明
- 原子化操作是解决这类并发问题的核心,依赖数据库本身的锁机制保证操作不可分割。
- 两种方法中,原子
UPDATE更简洁高效,推荐优先使用;事务+行锁适合需要更复杂业务判断的场景。 - 务必在任务执行完成后,将
IsBusy字段改回0,避免后续用户无法获取锁。
内容的提问来源于stack exchange,提问作者MasterOfNothing
相关产品推荐
相关产品推荐

