现实情况是,使用 Invoke 和朋友,您无法完全防止在已释放组件上调用,或者由于缺少句柄而导致 InvalidOperationException。在解决真正基本问题的任何线程中,我还没有真正看到答案,就像下面更远的那样,通过抢先测试或使用锁定语义无法完全解决。
这是正常的“正确”成语:
// the event handler. in this case preped for cross thread calls
void OnEventMyUpdate(object sender, MyUpdateEventArgs e)
{
if (!this.IsHandleCreated) return; // ignore events if we arn't ready, and for
// invoke if cant listen to msg queue anyway
if (InvokeRequired)
Invoke(new MyUpdateCallback(this.MyUpdate), e.MyData);
else
this.MyUpdate(e.MyData);
}
// the update function
void MyUpdate(Object myData)
{
...
}
根本问题:
在使用 Invoke 工具时,使用了 Windows 消息队列,它将消息放入队列中以等待或触发并忘记跨线程调用,就像 Post 或 Send 消息一样。如果在 Invoke 消息之前有一条消息会使组件及其窗口句柄无效,或者在您尝试执行的任何检查之后放置,那么您将度过一段糟糕的时光。
x thread -> PostMessage(WM_CLOSE); // put 'WM_CLOSE' in queue
y thread -> this.IsHandleCreated // yes we have a valid handle
y thread -> this.Invoke(); // put 'Invoke' in queue
ui thread -> this.Destroy(); // Close processed, handle gone
y thread -> throw Invalid....() // 'Send' comes back, thrown on calling thread y
没有真正的方法可以知道控件即将从队列中删除自己,并且没有什么真正合理的方法可以“撤消”调用。无论您进行多少检查或进行额外的锁定,您都无法阻止其他人发出诸如关闭或停用之类的东西。有很多场景会发生这种情况。
解决方案:
首先要意识到调用将失败,这与 (IsHandleCreated) 检查忽略事件的方式没有什么不同。如果目标是保护非 UI 线程上的调用者,您将需要处理异常,并将其视为任何其他未成功的调用(以防止应用程序崩溃或做任何事情。除非要重写/ reroll Invoke 工具,捕获是您唯一知道的方法。
// the event handler. in this case preped for cross thread calls
void OnEventMyWhatever(object sender, MyUpdateEventArgs e)
{
if (!this.IsHandleCreated) return;
if (InvokeRequired)
{
try
{
Invoke(new MyUpdateCallback(this.MyUpdate), e.MyData);
}
catch (InvalidOperationException ex) // pump died before we were processed
{
if (this.IsHandleCreated) throw; // not the droids we are looking for
}
}
else
{
this.MyUpdate(e.MyData);
}
}
// the update function
void MyUpdate(Object myData)
{
...
}
可以根据需要定制异常过滤。很高兴知道,在大多数应用程序中,工作线程通常没有所有轻松的外部异常处理和记录 UI 线程所做的事情,因此您可能希望只吞噬工作人员端的任何异常。或者记录并重新抛出所有这些。对于许多工作线程上未捕获的异常意味着应用程序将崩溃。