Timer阻塞主线程自动停止问题求助(附代码及Threading.Timer尝试)
问题分析与解决方案
原代码核心问题
- 主线程阻塞:原
System.Timers.Timer的SQL查询是同步执行,且后续UI操作未正确切换线程,导致主线程被阻塞;改用System.Threading.Timer后,用Dispatcher.Invoke<Task<...>>().Result强制等待异步任务,依然会阻塞线程。 - Timer意外停止:Timer回调中未捕获异常,一旦SQL查询或业务逻辑抛出未处理异常,Timer会直接终止;同时
AutoReset=false的手动重启逻辑存在漏洞,若某次执行未完成重启操作,Timer就会停掉。 - UI访问不安全:在非UI线程直接访问
Datagrid_Selected.Items等UI元素,会引发线程安全异常,间接导致Timer停止。
修正后的实现方案
1. 线程安全的Timer管理
用System.Threading.Timer配合异步回调,确保Timer状态管理线程安全,避免重复启动或意外停止:
private System.Threading.Timer _meetingTimer; private bool _isTimerRunning; private readonly object _timerLock = new object(); private const int _tickIntervalMs = 2000; // 恢复需求的2秒间隔 // 初始化Timer public void InitMeetingTimer() { _meetingTimer = new System.Threading.Timer(OnTimerCallback, null, Timeout.Infinite, Timeout.Infinite); } // 启动Timer(双重检查锁保证线程安全) public void StartTimer() { if (!_isTimerRunning) { lock (_timerLock) { if (!_isTimerRunning) { _isTimerRunning = true; _meetingTimer.Change(_tickIntervalMs, Timeout.Infinite); } } } } // 停止Timer public void StopTimer() { lock (_timerLock) { _isTimerRunning = false; _meetingTimer.Change(Timeout.Infinite, Timeout.Infinite); } }
2. 异步回调与安全操作
将所有IO操作(SQL查询/更新)异步化,用Dispatcher.InvokeAsync安全访问UI元素,同时添加异常捕获防止Timer终止:
private async void OnTimerCallback(object state) { try { // 异步获取管理员配置,避免阻塞线程 var adminSettings = await GetAdminSettingAsync("Daily_Duration"); if (adminSettings == null || adminSettings.Count == 0) { StopTimer(); return; } // 安全获取UI选中的日期(必须在UI线程执行) DateTime selectedDate = default; await Application.Current.Dispatcher.InvokeAsync(() => { if (Datagrid_Selected?.Items?.Count > 0) { selectedDate = ((Argo_Snowflake)Datagrid_Selected.Items[0]).Date_Created; } }); if (selectedDate == default) { _meetingTimer.Change(_tickIntervalMs, Timeout.Infinite); return; } // 异步获取会议数据 var dailyMeetings = await GetDailyMeetingAsync("", selectedDate); // 处理会议超时逻辑 if (dailyMeetings != null) { double durationThreshold = double.Parse(adminSettings[0].Setting_First_Value); foreach (var meeting in dailyMeetings) { if (meeting.End_Time == null) { var elapsedMinutes = Math.Round((DateTime.Now - meeting.Start_Time).TotalMinutes, 1); if (elapsedMinutes >= durationThreshold) { meeting.End_Time = DateTime.Now; meeting.Duration = elapsedMinutes; // 异步更新SQL,不阻塞Timer线程 await Task.Run(() => Function_Update_SQL.Update_Daily_Meeting(meeting)); // 线程安全更新本地状态 lock (Daily_Meeting_Summary) { Daily_Meeting_Summary[meeting.Division] = "True"; } } } } } // 在UI线程执行权限验证(涉及UI操作) await Application.Current.Dispatcher.InvokeAsync(VerifyUserPermission); // 判断是否继续运行Timer bool allMeetingsCompleted = false; lock (Daily_Meeting_Summary) { allMeetingsCompleted = Daily_Meeting_Summary.All(x => x.Value == "True"); } if (allMeetingsCompleted) { StopTimer(); } else if (_isTimerRunning) { _meetingTimer.Change(_tickIntervalMs, Timeout.Infinite); } } catch (Exception ex) { // 记录异常(替换成你的日志逻辑) // Console.WriteLine($"Timer回调异常: {ex.Message}"); // 异常后继续尝试运行Timer if (_isTimerRunning) { _meetingTimer.Change(_tickIntervalMs, Timeout.Infinite); } } }
3. 异步SQL查询封装
把同步SQL查询包装成异步方法,避免阻塞Timer线程:
private async Task<List<Admin_Settings>> GetAdminSettingAsync(string setting) { return await Task.Run(() => { switch (setting) { case "Daily_Duration": return Function_Select_SQL.Get_Admin_Settings("Daily_Duration"); case "Daily_Days_Setting": return Function_Select_SQL.Get_Admin_Settings("Daily_Days_Setting"); default: return null; } }); } private async Task<List<Daily_Meeting>> GetDailyMeetingAsync(string filter, DateTime date) { return await Task.Run(() => Function_Select_SQL.Get_Daily_Meeting("", "", date)); }
4. 修正后的权限验证函数
确保UI操作在UI线程执行,同时正确控制Timer和UI状态:
private void VerifyUserPermission() { var adminSettings = Function_Select_SQL.Get_Admin_Settings("Daily_Days_Setting"); if (adminSettings == null || adminSettings.Count == 0) { StopTimer(); return; } string[] dailyDays = adminSettings[0].Setting_First_Value.Split(','); if (!dailyDays.Contains(DateTime.Now.DayOfWeek.ToString())) { StopTimer(); return; } // 线程安全更新会议状态摘要 lock (Daily_Meeting_Summary) { if (Daily_Meeting_Summary.Count == 0) { foreach (var division in List_Divisions) { var meetings = Function_Select_SQL.Get_Daily_Meeting("", division, DateTime.Now); Daily_Meeting_Summary.Add(division, meetings == null || meetings[0].End_Time == null ? "False" : "True"); } } else if (!string.IsNullOrEmpty(Division_Selected)) { var meetings = Function_Select_SQL.Get_Daily_Meeting("", Division_Selected, DateTime.Now); Daily_Meeting_Summary[Division_Selected] = meetings == null || meetings[0].End_Time == null ? "False" : "True"; } } // 控制UI显示 bool allCompleted = Daily_Meeting_Summary.All(x => x.Value == "True"); if (allCompleted) { StartDailyButton.Visibility = Visibility.Collapsed; Daily_Meeting_sb.Begin(); // 启动闪烁动画 StopTimer(); } else { StartDailyButton.Visibility = Visibility.Visible; if (!_isTimerRunning) { StartTimer(); } } }
关键注意事项
- 线程安全:所有共享集合(如
Daily_Meeting_Summary)的读写必须加锁,避免多线程冲突。 - 异步优先:所有IO操作(SQL、文件等)都要异步执行,不要用
.Result或.Wait()阻塞线程。 - 异常捕获:Timer回调必须捕获所有异常,否则一次未处理的异常会导致Timer永久停止。
- UI隔离:任何UI元素的访问和操作必须通过
Dispatcher切换到UI线程执行。
内容的提问来源于stack exchange,提问作者dadou
相关产品推荐
相关产品推荐

