c# - 计时器对象存在于窗口对象生命周期之外

标签 c#

我将 Forms 窗口显示为对话框

private void buttonOverview_Click(object sender, EventArgs e)
{
    (new OverviewBox()).ShowDialog();
    MessageBox.Show("Window Exited");
}

OverviewBox 有一个在构造函数中实例化的刷新计时器

public OverviewBox()
{
    InitializeComponent();

    this._polltimer = new Timer { Interval = 30000, Enabled = true };
    this._polltimer.Tick += (sender, e) => { this.Poll(); };
}

Poll 方法异步从数据库中获取数据并更新 View 而不卡住它。

private void Poll()
{
    Task.Run(() =>
    {
        if (!SessionContext.Connectable())
        {
            return;
        }
        try
        {
            [logics to get data]
            this.dgvChangeCoordinators.BeginInvoke(new Action(() => { SetDataGridView(this.dataGridView, "<Data Description>", listwithdata); }));
        }
        catch (Exception ex)
        {
            Logger.Log(ex.ToString());
            throw;
        }
    });
}

SetDataGridView 将列表设置为 datagridviewitemsource 并显示数据描述。然而,有时我的用户会提示异常。异常日志如下所示:

7/15/2013 5:00:10 PM:
System.InvalidOperationException: Invoke or BeginInvoke cannot be called on a control until the window handle has been created.
at System.Windows.Forms.Control.MarshaledInvoke(Control caller, Delegate method, Object[] args, Boolean synchronous)
at System.Windows.Forms.Control.BeginInvoke(Delegate method, Object[] args)
at System.Windows.Forms.Control.BeginInvoke(Delegate method)
at FormulierGenerator.Views.Agent.OverviewBox.<Poll>b__5()


7/15/2013 5:00:23 PM:
System.InvalidOperationException: Invoke or BeginInvoke cannot be called on a control until the window handle has been created.
at System.Windows.Forms.Control.MarshaledInvoke(Control caller, Delegate method, Object[] args, Boolean synchronous)
at System.Windows.Forms.Control.BeginInvoke(Delegate method, Object[] args)
at System.Windows.Forms.Control.BeginInvoke(Delegate method)
at FormulierGenerator.Views.Agent.OverviewBox.<Poll>b__5()


7/15/2013 5:00:40 PM:
System.InvalidOperationException: Invoke or BeginInvoke cannot be called on a control until the window handle has been created.
at System.Windows.Forms.Control.MarshaledInvoke(Control caller, Delegate method, Object[] args, Boolean synchronous)
at System.Windows.Forms.Control.BeginInvoke(Delegate method, Object[] args)
at System.Windows.Forms.Control.BeginInvoke(Delegate method)
at FormulierGenerator.Views.Agent.OverviewBox.<Poll>b__5()


7/15/2013 5:00:53 PM:
System.InvalidOperationException: Invoke or BeginInvoke cannot be called on a control until the window handle has been created.
at System.Windows.Forms.Control.MarshaledInvoke(Control caller, Delegate method, Object[] args, Boolean synchronous)
at System.Windows.Forms.Control.BeginInvoke(Delegate method, Object[] args)
at System.Windows.Forms.Control.BeginInvoke(Delegate method)
at FormulierGenerator.Views.Agent.OverviewBox.<Poll>b__5()

根据异常之间的时间差,我得出结论,至少有两个计时器实例仍然处于事件状态(轮询间隔 30 秒,4 次不同的时间,其中 2 组在 30 秒内,即轮询间隔)。但是,我无法通过仅启动和关闭概述两次来模拟该问题。

我怀疑与 GC 相关的问题,窗口对象在某个时间点被收集,但轮询器一直存在。当它尝试更新窗口线程上下文中的窗口时,它失败了。但是,Window 对象及其所有内容难道不应该只存在于 private void buttonOverview_Click 的上下文中吗?添加了对按钮方法的 MessageBox.Show() 调用,以测试该方法是否在关闭对话框后完成。它确实显示了。

Poll 方法上设置一个断点,以查看在对话框关闭后它是否仍然被调用。是的,所以轮询者的生命周期肯定比窗口可见的时间长。我的问题是,到目前为止我的结论是否正确?如果是这样,即使创建计时器的对象已被实例化的上下文不再存在,轮询器如何继续存在,例如如何防止轮询者在窗口关闭的路上继续存在?考虑卸载事件操作,但不知道这是否是最佳解决方案。

最佳答案

首先,垃圾收集器不是确定性的。即使您的窗口已关闭且未在任何地方引用,在实际收集窗口之前也可能需要很长时间。您应该在窗口关闭后立即取消订阅 Tick 事件并将 IsEnabled 设置为 false

也就是说,这里真正的问题是 System.Windows.Forms.Timer 本身。一旦启用,它就会分配一个 GCHandle。对于它自己,阻止它的垃圾收集。然后,事件处理程序会阻止收集窗口,而不是像通常情况和您认为正在发生的那样以其他方式收集。

请注意 System.Windows.Forms.Timer 在处理时禁用自身,以防止出现此问题,并且 Form 的所有组件在表单关闭时自动处理。但是您没有将 Timer 注册为表单组件,因此永远不会自动调用 Dispose。您应该通过工具箱将 Timer 添加到表单中,或者使用 new Timer(components) 实例化它以查看问题是否消失。

关于c# - 计时器对象存在于窗口对象生命周期之外,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17838070/

相关文章:

c# - 获取 Web 服务应用程序和 Windows 服务使用的程序集中的当前目录

c# - 如何在 .NET Core 应用程序 docker 镜像中包含依赖项?

c# - 如何为 SQLite 创建身份服务器数据库?

c# - 我的自定义 Exceptions 应该继承与它们类似的异常还是仅继承自 Exception?

c# - 使用文件选择器 Metro 应用程序选择所有文件 Windows 8

c# - TEXT 字段中的错误 (PostgreSQL) - NHibernate 无法读取它

c# - 检索设备的 IP 地址

c# - 仅在 C# 代码中出现 SQL 语法错误

C#从命令行运行窗体程序时隐藏窗体

c# - 使用 base.Any(..) 警告是 : 'HashSet' does not contain a definition for 'Any'