.NET Framework 4.0并发调用存储过程生成CSV文件缺失问题排查
问题排查:并发调用存储过程生成CSV仅部分成功
核心问题分析
1. 数据库连接管理不当
代码中创建SqlConnection实例后,依赖DB.Connect_SQLDataTable处理连接,但如果该方法未正确打开、关闭或释放连接,并发场景下会导致连接池耗尽,部分任务无法获取连接执行存储过程,最终返回空数据。
2. UI控件线程不安全访问
divHtml.InnerHtml是ASP.NET WebForms服务器控件,多线程(Task)直接修改会触发跨线程访问异常(部分场景下异常被静默吞掉,但会中断后续逻辑),导致任务提前终止,无法生成文件。
3. SQL拼接的日期格式风险
直接将strDate拼接进SQL语句,即便用了yyyy-MM-dd格式,若存储过程对日期参数格式验证严格,或数据库语言/日期设置不匹配,会导致参数解析错误,存储过程返回空结果,同时存在SQL注入风险。
4. 异常处理不完整
文件创建的try/catch仅返回空字符串,未记录错误信息;DB.Connect_SQLDataTable返回false时,也未处理strErr,无法定位数据库层面的失败原因。
修复方案
1. 正确管理数据库连接
用using语句包裹连接,确保自动释放,同时改用参数化查询避免格式问题:
string SQL = "EXEC [BMC_LoadPatientServices_New] @Date, '', '', '%He%', '%OPD%'"; string strErr = string.Empty; using (SqlConnection con = new SqlConnection(DB.GetConnectionString())) { using (SqlCommand cmd = new SqlCommand(SQL, con)) { cmd.Parameters.Add("@Date", SqlDbType.Date).Value = DateTime.Parse(strDate); try { con.Open(); SqlDataAdapter da = new SqlDataAdapter(cmd); DataTable dtView = new DataTable(); da.Fill(dtView); // 后续dtView处理逻辑... } catch (Exception ex) { strErr = ex.Message; // 写入错误日志,比如本地文本文件 } } }
2. 避免跨线程修改UI控件
改用线程安全容器收集状态,任务完成后统一更新UI:
// 定义线程安全容器 ConcurrentBag<string> taskStatuses = new ConcurrentBag<string>(); // 在PopulateDateData中替换divHtml修改逻辑: // divHtml.InnerHtml += ", Date: " + strDate; taskStatuses.Add(", Date: " + strDate); // 所有任务完成后统一更新UI Task.WaitAll(taskArray); divHtml.InnerHtml += string.Join("", taskStatuses);
3. 完善异常处理与日志
在关键步骤添加日志记录,方便定位失败原因:
// 文件创建异常处理优化 try { using (FileStream fs = File.Create(file)) { Byte[] title = new UTF8Encoding(true).GetBytes(builder.ToString()); fs.Write(title, 0, title.Length); } } catch (Exception ex) { File.AppendAllText(Server.MapPath("~/Logs/csv_error.log"), $"Date: {strDate}, Error: {ex.Message}{Environment.NewLine}"); return $"Error: {ex.Message}"; } // 数据库连接失败时记录日志 if (!DB.Connect_SQLDataTable(ref dtView, SQL, ref con, ref strErr)) { File.AppendAllText(Server.MapPath("~/Logs/db_error.log"), $"Date: {strDate}, DB Error: {strErr}{Environment.NewLine}"); }
4. 优化Task创建逻辑
确保每个Task的日期参数被正确捕获,避免闭包陷阱:
Task<string>[] taskArray = new Task<string>[5]; DateTime currentDate = DateTime.Now.Date; // 仅保留日期部分,避免时间干扰 for (int i = 0; i < taskArray.Length; i++) { string targetDate = currentDate.AddDays(-(i+1)).ToString("yyyy-MM-dd"); taskArray[i] = Task.Factory.StartNew(() => PopulateDateData(targetDate)); } Task.WaitAll(taskArray);
内容的提问来源于stack exchange,提问作者vickyshazad
相关产品推荐
相关产品推荐

