You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 06:20:33