使用MySqlDataReader轮询数据库重复报错:存在未关闭的关联DataReader
问题分析与解决方法
核心问题
你的代码存在几个关键问题导致重复查询时出现DataReader冲突:
- 类级别的
conn变量被多线程(Timer的Elapsed事件)共享,前一次数据库操作未完全释放,后一次就复用连接,引发资源冲突。 - 你把单次查询逻辑写在了
startPoll方法里,但Timer的OnEventExecution才是轮询的执行入口,推测该事件里的数据库操作复用了同一个未正确隔离的连接对象。 - 手动调用
rdr.Close()和conn.Close()属于多余操作,using语句会自动释放资源,手动关闭反而可能导致资源释放时机混乱。
修复步骤
1. 完全隔离每次轮询的数据库资源
每次轮询都创建独立的连接、命令和DataReader,不要复用类级别的连接变量。把数据库查询逻辑封装到单独的方法中,确保每次调用都使用全新的局部资源:
private void OnEventExecution(object sender, ElapsedEventArgs e) { // 跨线程更新UI需要Invoke if (stateText.InvokeRequired) { stateText.Invoke(new Action(() => { stateText.Text = "Running"; stateText.ForeColor = System.Drawing.Color.Green; })); } else { stateText.Text = "Running"; stateText.ForeColor = System.Drawing.Color.Green; } string connectionString = $"server={Properties.Settings.Default.DbServer};database=digital_passports;uid={Properties.Settings.Default.DbUser};pwd={Properties.Settings.Default.DbPass}"; try { using (var conn = new MySqlConnection(connectionString)) { conn.Open(); System.Diagnostics.Debug.WriteLine("Database Connection Connected"); var stm = "SELECT * from vtokenpool limit 10"; using (var cmd = new MySqlCommand(stm, conn)) { using (var rdr = cmd.ExecuteReader()) { while (rdr.Read()) { System.Diagnostics.Debug.WriteLine($"{rdr[0]} -- {rdr[1]}"); } } } } // using结束自动关闭conn和释放资源 } catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex.ToString()); // 出错时更新UI状态 if (stateText.InvokeRequired) { stateText.Invoke(new Action(() => { stateText.Text = "Error"; stateText.ForeColor = System.Drawing.Color.Red; })); } else { stateText.Text = "Error"; stateText.ForeColor = System.Drawing.Color.Red; } } }
2. 修正startPoll方法的逻辑
startPoll只负责初始化和启动Timer,不要在里面执行单次查询:
private void startPoll() { System.Diagnostics.Debug.WriteLine("dbForm created"); int pollInterval = int.Parse(Properties.Settings.Default.DbPoll) * 1000; timer = new Timer(pollInterval); timer.Elapsed += OnEventExecution; timer.Start(); System.Diagnostics.Debug.WriteLine($"Timer Started {Properties.Settings.Default.DbPoll} secs"); }
3. 移除多余的手动关闭操作
using语句会在代码块结束时自动调用对象的Dispose方法,自动关闭DataReader和Connection,无需手动调用rdr.Close()或conn.Close(),避免资源释放顺序混乱。
4. 确保Timer线程安全
Timer的Elapsed事件是在ThreadPool线程中触发的,直接操作UI控件会引发跨线程异常,必须使用Invoke来更新UI。
额外建议
- 考虑使用
async/await异步操作数据库,避免阻塞UI线程:将查询方法改为异步,在OnEventExecution中调用异步方法(注意Timer的异步处理需要用async void,但要做好异常捕获)。 - 添加连接超时设置:在连接字符串中加入
ConnectionTimeout=10,避免连接挂起占用资源。
内容的提问来源于stack exchange,提问作者Sudomasochist
相关产品推荐
相关产品推荐

