我正在显示一个Forms窗口作为对话框
private void buttonOverview_Click(object sender, EventArgs e)
{
(new OverviewBox()).ShowDialog();
MessageBox.Show("Window Exited");
}
Run Code Online (Sandbox Code Playgroud)
OverviewBox 有一个刷新计时器,在构造函数中实例化
public OverviewBox()
{
InitializeComponent();
this._polltimer = new Timer { Interval = 30000, Enabled = true };
this._polltimer.Tick += (sender, e) => { this.Poll(); };
}
Run Code Online (Sandbox Code Playgroud)
该方法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;
}
});
}
Run Code Online (Sandbox Code Playgroud)
SetDataGridView将列表设置为itemsourcea datagridview并显示数据描述.但有时候,我的用户会抱怨异常.异常日志如下所示:
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()
Run Code Online (Sandbox Code Playgroud)
根据异常之间的时间差,我得出结论,至少有两个定时器实例仍处于活动状态(轮询之间为30秒,4个不同时间为2个组在30秒内,轮询间隔).但是,我无法通过两次启动和关闭概述来模拟问题.
我怀疑GC在某个时间点收集窗口对象的相关问题,但是轮询器继续存在.当它尝试更新窗口线程上下文中的窗口时,它会失败.但那么,Window对象及其所有内容是否只存在于private void的上下文中buttonOverview_Click?添加MessageBox.Show()调用button方法以测试关闭对话框后方法是否完成.确实显示出来.
在Poll方法上设置断点,以查看在关闭对话框后是否仍然调用它.它是,所以轮询者肯定活得更长,然后窗口是可见的.我的问题是,到目前为止我的结论是否正确?如果是这样,即使已经实例化了创建计时器的对象的上下文,轮询器如何继续存在,例如如何防止轮询器在窗口关闭之后生活?考虑卸载事件操作但不知道这是否是最佳解决方案.
首先,垃圾收集器不是确定性的.即使您的窗口已关闭且未在任何地方引用,在实际收集窗口之前可能需要很长时间.你应该从取消Tick事件,并IsEnabled以false尽快关闭窗口.
话虽如此,这里真正的问题是它System.Windows.Forms.Timer本身.一旦启用,它就会GCHandle为自己分配一个,以防止其垃圾收集.事件处理程序然后阻止窗口被收集,而不是通常情况下的其他方式,并且正如您所想的那样.
请注意,System.Windows.Forms.Timer在处理时会禁用自身,以防止出现此问题,并且Form在表单关闭时会自动处理a的所有组件.但是你没有注册Timer为表单组件,因此Dispose永远不会自动调用.您应该Timer通过工具箱向表单添加一个,或者使用它来实例化new Timer(components)以查看问题消失.