C# 关闭窗体时ObjectDisposedException

C# 关闭窗体时ObjectDisposedException,c#,winforms,C#,Winforms,我在WinForm上有一个计时器,在窗体加载时启动该计时器: private void MainForm_Load(object sender, EventArgs e) { Action action = () => lblTime.Text = DateTime.Now.ToLongTimeString(); Task task = new Task(() => { while (true) { I

我在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();
}
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);
}
但问题仍然存在。我做错了什么

更新:我知道WinForms有一个标准计时器,它在多线程环境中工作得非常好。我只是想知道如何才能让它更好地理解如何应对比赛条件。这种计时器只是一个例子,它可能是另一个需要更新GUI的进程

更新2:以下是我找到的代码和答案:

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();
}
但是无论如何,如果我设置时间间隔,例如100ms,ObjectDisposedException仍然抛出


这不是真实的示例,我只是在尝试…

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

尽管在第二个示例中尝试向任务发出警报,但它实际上并不是线程安全的,并且在释放控件后仍然可以调用该操作

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

使用WinForm计时器,您不需要担心线程或
任务
s,更重要的是,如果您需要更新UI(与子线程或非UI上下文任务相反),您不需要担心
调用

告诉我更多

好的,我尝试通过以下方式使用任务取消:

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();
}
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毫秒,它似乎也能工作。

问题是,在调用
调用(操作)
和调用将回调消息发布到消息队列之间,表单已关闭。这是一个经典的比赛条件。不幸的是,这很难可靠地修复。您可能必须捕获并忽略该特定异常(抖动)。请注意,在
mainformclosing()
中,您不能做任何聪明的事情,比如尝试等待正在进行的调用完成,因为这样您可能会导致死锁。我的第一个想法是:显示日期时间不会在尝试时造成严重破坏。忽略捕获。但是考虑使用一个计时器(事件驱动)来更新你的控制。你需要在关闭窗体之前停止这些任务,或者你的任务必须意识到窗体被关闭/关闭。问题是,您仍然试图在表单上
调用(操作)
,在表单关闭后,这只是一个简单的时间问题。由于您没有将
\u runningTimer
设置为volatile,编译器可以对此进行优化,但即使将其设置为volatile,也可能无法保证其正常工作。是的,基于窗口的计时器可能是最好的方法。您知道什么是
任务。延迟(1000)是什么?没有什么。它不会等一秒钟。您必须等待任务。延迟(1000)
Task.WaitAll(Task.Delay(1000))“如果窗口正在关闭,则不会触发”-严格来说,这不是真的。如果计时器已连接到窗体组件,则当窗体关闭时,计时器将关闭,从而阻止其启动。如果窗体关闭,计时器就不会开火,这并没有什么神奇之处。