WinForms多线程更新进度条时遇已释放对象访问错误求助
解决WinForms关闭时“访问已释放对象”错误
问题根源
后台线程在await Task.Delay期间,窗口可能已经被关闭并释放控件资源,但线程恢复后仍会尝试调用UI更新方法,此时Invoke需要访问已释放的控件句柄,导致异常。仅检查IsDisposed无法覆盖await期间窗口被释放的场景。
修改方案
1. 引入任务取消机制
用CancellationTokenSource管理后台任务,窗口关闭时主动取消任务,避免后续代码执行。
2. 优化UI更新前的检查
在调用UI更新方法前,先检查控件是否处于可用状态(未释放且句柄已创建)。
3. 改进异步方法定义
将async void的后台方法改为async Task,便于任务生命周期管理。
修改后的代码示例
窗体类中添加取消令牌字段
private CancellationTokenSource _cts; private Task _backgroundTask;
重构后台任务方法
private async Task BackgroundWorkerAsync(CancellationToken token) { if (string.IsNullOrEmpty(m_Arguments.m_ProgressFilePath)) { MessageBox.Show(GetValueFromResourceFile("WarningProgressPathNotProvided")); CloseForm(); return; } while (!IsFinsihed() && !token.IsCancellationRequested) { token.ThrowIfCancellationRequested(); string progressText = ReadTextFromFile(m_Arguments.m_ProgressFilePath); string msgText = ReadTextFromFile(m_Arguments.m_MsgFilePath); int progress = 0; if (!string.IsNullOrEmpty(progressText)) { TryUpdateSingleValue(progressText, ref progress); } if (!string.IsNullOrEmpty(msgText) && !IsMessageTooLong(msgText)) { // 更新前检查控件状态 if (!processingInfo.IsDisposed && processingInfo.IsHandleCreated) { UpdateMsgText(msgText); m_MessageBoxTooLongTextShown = false; } } if (progress >= 100) { CloseForm(); break; } if (progress < 0) { if (!progressBar.IsDisposed && progressBar.IsHandleCreated) { UpdateProgressBar(0); } } // 延迟期间响应取消请求 await Task.Delay(m_Arguments.m_ThreadDelayTime, token); } }
优化UI更新方法
private void UpdateMsgText(string msgText) { // 先判断控件是否可用 if (processingInfo.IsDisposed || !processingInfo.IsHandleCreated) return; if (InvokeRequired) { try { // 使用BeginInvoke避免阻塞,同时捕获可能的异常 BeginInvoke(new MethodInvoker(() => UpdateMsgText(msgText))); } catch (ObjectDisposedException) { return; } } else { processingInfo.Text = msgText; } }
窗体关闭时取消任务
private void YourForm_FormClosing(object sender, FormClosingEventArgs e) { _cts?.Cancel(); // 等待后台任务完成后再关闭窗口 _backgroundTask?.Wait(); }
启动后台任务(如窗体加载事件)
private void YourForm_Load(object sender, EventArgs e) { _cts = new CancellationTokenSource(); _backgroundTask = BackgroundWorkerAsync(_cts.Token); }
关键说明
CancellationToken能在窗口关闭时立即中断后台任务,避免线程继续执行UI更新逻辑。- 每次调用UI方法前检查
IsDisposed和IsHandleCreated,确保控件处于可用状态。 - 用
BeginInvoke替代Invoke(或在Invoke外层加try-catch),可以处理调用过程中控件被释放的场景。
内容的提问来源于stack exchange,提问作者User23423
相关产品推荐
相关产品推荐

