3

我将一个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从数据库中异步获取数据并更新视图而不冻结它。

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将列表设置为itemsourceadatagridview并显示数据描述。但是,有时我的用户会抱怨异常。异常日志如下所示:

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以查看在关闭对话框后是否仍调用它。确实如此,因此轮询者的寿命肯定比窗户可见的时间更长。我的问题是,到目前为止我的结论是否正确?如果是这样,即使创建计时器的对象已被实例化的上下文不再存在,轮询器如何继续存在,例如如何防止轮询器在窗口关闭后继续存在?考虑卸载事件操作,但不知道这是否是最佳解决方案。

4

2 回答 2

5

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

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

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

于 2013-07-24T16:30:55.037 回答
0

需要把tick变成命名方法

this._polltimer.Tick += _polltimer_Tick;

并在关闭表单时取消注册

    private void OverviewBox_FormClosed(object sender, FormClosedEventArgs e)
    {
        this._polltimer.Tick -= this._polltimer_Tick;
    }

不注销可以防止 Timer 被销毁,因为仍然有对它的活动引用。然而,关闭表单会破坏表单上的元素,从而导致调用调用时出现问题。

非常感谢所有特别回复@BenjaminBaumann 的人,他们提供了导致这个答案的线索。

于 2013-07-24T16:25:30.113 回答