我有一种包含 GridView 和一些实用程序按钮的控件。该控件在我的应用程序中随处使用。它通过委托异步填充:
protected virtual void PopulateGridView()
{
if (isPopulating) return;
//a delegate given to the control by its parent form
if (GetterMethod != null)
{
isPopulating = true;
/*unimportant UI fluff here*/
//some controls are fast enough to not have to mess with threading
if(PopulateSynchronously)
{
PopulateWithGetterMethod();
InitializeGridView();
}
else //most aren't
{
Action asyncMethod = PopulateWithGetterMethod;
asyncMethod.BeginInvoke(
ar => Invoke((MethodInvoker)InitializeGridView)), null);
}
}
}
private void PopulateWithGetterMethod()
{
//a list of whetever the control is displaying;
//the control ancestor and this collection are generic.
RetrievedInformation = GetterMethod();
}
protected virtual void InitializeGridView()
{
//use RetrievedInformation to repopulate the GridView;
//implementation not important, except it touches UI elements,
//so it needs to be called from the worker thread using Invoke.
}
在长时间运行的查询中,有时用户会不耐烦并关闭窗口。或者,当其中一个控件基于 Timer 自动刷新时,用户会偶然关闭一个窗口。当这种情况发生并且查询 DID 完成时,回调委托中的 Invoke 调用将失败并出现 InvalidOperationException,因为控件没有窗口句柄。
为了解决这个问题,我尝试使用内置的 IsHandleCreated 属性:
...
else
{
Action asyncMethod = PopulateWithGetterMethod;
asyncMethod.BeginInvoke(
ar => { if(IsHandleCreated)
Invoke((MethodInvoker)InitializeGridView));
}, null);
}
但是,异常仍然会发生,只是不那么频繁。我设法重现它,发现 Invoke 调用仍然发生,即使 IsHandleCreated 上的手表显示为假。我的猜测是,线程在检查和 Invoke 调用之间被抢占了,就像您在提升它之前检查事件委托是否为 null 一样。
我认为我仍然有选择,但我想知道最好的是什么:
- 不仅要检查 IsHandleCreated,还要检查 Disposing,以确保控件确实存在且完好无损,而不仅仅是即将被销毁。
- 在进行检查之前执行 Thread.Yield(),以允许操作系统在检查句柄之前进行任何窗口管理。
- 将 Invoke 调用包装在抑制任何 InvalidOperationExceptions 或至少报告缺少窗口句柄的 try/catch 中。老实说,在这种情况下,我不在乎 GridView 不能更新;用户关闭了窗口,所以显然他们不在乎。让线程安静地死去,而不需要关闭整个应用程序。
第三种选择似乎是一种逃避。必须有一种更清洁的方法来处理它。但是,我不确定其他两个中的任何一个都将是 100% 修复。
编辑:检查 Disposing 和 IsDisposed 也不起作用;我从 if 块中抛出了一个异常,条件为“IsHandleCreated && !Disposing && !IsDisposed”,其中第一个和最后一个节点在观察时为假。目前,我正在使用消息“在创建窗口句柄之前无法在控件上调用 Invoke 或 BeginInvoke。”来捕获所有异常,这是我不希望这样做的。