猿问

关闭表单时出现 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 Passant和Inigmativity 的答案,我来到了该代码:


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 仍然会抛出。


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


长风秋雁
浏览 192回答 2
2回答

小唯快跑啊

好的,我尝试通过以下方式使用任务取消:public MainForm(){    InitializeComponent();    cts = new CancellationTokenSource();    Load += MainForm_Load;    FormClosing += MainForm_FormClosing;}private void MainForm_FormClosing(object sender, FormClosingEventArgs e){    cts.Cancel();}private void MainForm_Load(object sender, EventArgs e){            CancellationToken ct = cts.Token;    Action action = () => { lblTime.Text = DateTime.Now.ToLongTimeString(); };    var task = Task.Factory.StartNew(async () => {        ct.ThrowIfCancellationRequested();        while (true)        {            Invoke(action);            await Task.Delay(100);        }    }, ct);}不知道是否正确,但即使将时间间隔设置为 10 毫秒,它似乎也有效。

幕布斯6054654

在您的第一个示例中,任务不知道您的应用程序正在退出,并且在标签销毁后有调用操作的风险,因此ObjectDisposedException.尽管您尝试在第二个示例中提醒任务,但它并不是真正的线程安全,并且您仍然可以在处理控件后调用该操作。计时器更好的解决方案是仅使用 WinForms Timer。如果您通过设计器将计时器放置在表单上,它会自动将其注册为组件依赖项,从而使生命周期管理变得更加容易。使用 WinForm 计时器,您无需担心线程或Tasks,更重要的是,您无需担心Invoke是否需要更新 UI(与子线程或非 UI 上下文任务相反)
随时随地看视频慕课网APP
我要回答