首页 > 解决方案 > 关闭表单时出现 ObjectDisposedException

问题描述

我在 WinForm 上有一个计时器,它在表单加载时启动:

private void MainForm_Load(object sender, EventArgs e)    
{
    Action action = () => lblTime.Text = DateTime.Now.ToLongTimeString();

    Task task = new Task(() => {
        while (true)
        {
            Invoke(action);
            Task.Delay(1000);
        }
    });
    task.Start();
}

问题是当我在 VS 中以调试模式启动应用程序并关闭它时。我得到一个 ObjectDisposedException ,它表明我的表单已经被释放。

我尝试通过以下方式修复它:

private bool _runningTimer = true;

public MainForm()
{
    InitializeComponent();

    // ...
    FormClosing += MainForm_FormClosing;
}

private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
    _runningTimer = false;
}

private void MainForm_Load(object sender, EventArgs e)
{
    Action action = () => lblTime.Text = DateTime.Now.ToLongTimeString();

    Task task = new Task(() => {
        while (_runningTimer)
        {
            Invoke(action);
            Task.Delay(1000);
        }
    });
    task.Start();
}

但问题仍然存在。我在这里做错了什么?

更新:我知道 WinForms 有一个标准计时器,它在多线程环境中工作得很好。我只是想知道如何使它工作以更好地理解如何处理竞争条件。这种定时器只是一个例子,它可能是另一个需要更新 GUI 的进程。

更新 2:按照Hans PassantInigmativity的答案,我来到了那个代码:

private void MainForm_Load(object sender, EventArgs e)
{
    Action action = () => { lblTime.Text = DateTime.Now.ToLongTimeString(); };

    Task task = new Task(async () => {
        while (!IsDisposed)
        {
            Invoke(action);
            await Task.Delay(1000);
        }
    });
    task.Start();
}

但无论如何,如果我设定时间间隔,例如 100 毫秒,ObjectDisposedException 仍然会抛出。

这不是现实生活中的例子,我只是在试验它......

标签: c#winforms

解决方案


在您的第一个示例中,任务不知道您的应用程序正在退出,并且在标签被销毁有调用操作的风险,因此ObjectDisposedException.

尽管您尝试在第二个示例中提醒任务,但它并不是真正的线程安全,您仍然可以在释放控件后调用该操作。

计时器

更好的解决方案是只使用 WinForms Timer。如果您通过设计器将计时器放置在表单上,​​它会自动将其注册为组件依赖项,从而使生命周期管理更加容易。

使用 WinForm 计时器,您无需担心线程或Tasks,更重要的是,您无需担心Invoke是否需要更新 UI(与子线程或非 UI 上下文任务相反)

告诉我更多


推荐阅读