C#后台服务同步电表历史数据MySQL写入行数超出预期问题咨询
问题根因
1. StringBuilder未每次循环清空(核心原因,完全匹配你给出的行数异常规律)
你将SqlQuery实例化写在了day循环的外层,且每次循环结束后没有清空内容:
- 第1次day循环:拼接1条INSERT语句,执行插入1行,累计1行
- 第2次day循环:在上一次的基础上追加第2条INSERT,此时SQL包含2条语句,执行插入2行,累计3行
- 第3次day循环:再追加第3条INSERT,此时SQL包含3条语句,执行插入3行,累计6行
最终总插入行数为1+2+...+n = n(n+1)/2,和你描述的数值完全吻合:n=3得6、n=4得10、n=5得15、n=6得21。
2. 循环变量闭包捕获问题
你在foreach循环中直接将捕获了Router、Meter循环变量的委托加入actionList,C# 5.0之前的版本中,foreach循环的迭代变量是全局作用域,所有委托捕获的都是同一个变量的引用,等到Parallel.Invoke执行时,变量值已经被后续迭代覆盖,会导致同一个电表被重复执行多次插入。
3. async void方法 + Parallel.Invoke 逻辑错误
GetHistory被声明为async void,这类异步方法无法被调用方追踪执行状态,Parallel.Invoke只会等待委托的同步部分执行完成,不会等待异步IO结束,就会执行后续的actionList.Clear()、进入下一轮60秒周期的循环,导致上一轮还没执行完的插入逻辑和新一轮的逻辑叠加,重复插入数据。
4. 模型实例未重置
mcReadings实例化在day循环外层,每次循环只会覆盖属性值,若部分属性未被赋值,会携带上一次循环的旧值,导致错误数据插入,极端情况也会触发重复插入逻辑。
修复方案
- 每次day循环清空StringBuilder,或者直接将
SqlQuery的实例化移到day循环内部:
for (int day = 1; day < 4; day++) { StringBuilder SqlQuery = new StringBuilder(); // 剩余逻辑不变 }
- 修复闭包问题,在循环内定义局部变量保存迭代值:
foreach (PowerMeterModel Meter in Meters.Where(x => x.IdGateway == Router.Id).ToList()) { if (Meter.IsActive) { // 定义局部变量捕获当前迭代的值 var currentRouter = Router; var currentMeter = Meter; actionList.Add(new Action(() => { GetHistory(currentRouter, currentMeter); _logger.LogInformation("Synced History meter:" + currentMeter.SerialNumber.ToString()); })); } }
- 替换async void为async Task,改用异步并行等待,不要用Parallel.Invoke处理IO密集型任务:
// 将GetHistory签名改为 public async Task GetHistory(RouterModel Router, PowerMeterModel Meter) { // 原有逻辑不变 } // 执行部分替换Parallel.Invoke为: var tasks = actionList.Select(action => Task.Run(action)); await Task.WhenAll(tasks);
- 将mcReadings实例化移到day循环内部,避免旧值残留。
- 不要手动拼接SQL语句,改用参数化查询,避免SQL注入风险。
内容的提问来源于stack exchange,提问作者Tom Boudniatski
相关产品推荐
相关产品推荐

