在调用`Invoke`时避免`ObjectDisposedException`

我有2个表单,一个是MainForm ,第二个是DebugForm 。 MainForm有一个按钮,可以像这样设置和显示DebugForm,并传递对已经打开的SerialPort的引用:

 private DebugForm DebugForm; //Field private void menuToolsDebugger_Click(object sender, EventArgs e) { if (DebugForm != null) { DebugForm.BringToFront(); return; } DebugForm = new DebugForm(Connection); DebugForm.Closed += delegate { WindowState = FormWindowState.Normal; DebugForm = null; }; DebugForm.Show(); } 

在DebugForm中,我附加了一个方法来处理serialport连接的DataReceived事件(在DebugForm的构造函数中):

 public DebugForm(SerialPort connection) { InitializeComponent(); Connection = connection; Connection.DataReceived += Connection_DataReceived; } 

然后在Connection_DataReceived方法中,我更新DebugForm中的TextBox,即使用Invoke进行更新:

 private void Connection_DataReceived(object sender, SerialDataReceivedEventArgs e) { _buffer = Connection.ReadExisting(); Invoke(new EventHandler(AddReceivedPacketToTextBox)); } 

但我有一个问题。 一旦我关闭DebugForm,它就会在Invoke(new EventHandler(AddReceivedPacketToTextBox));上抛出一个ObjectDisposedException Invoke(new EventHandler(AddReceivedPacketToTextBox)); 线。

我怎样才能解决这个问题? 欢迎任何提示/帮助!

UPDATE

我发现如果在按钮事件中单击删除事件,并在该按钮单击中关闭表单,一切都很好,我的调试表关闭,没有任何exception……多么奇怪!

 private void button1_Click(object sender, EventArgs e) { Connection.DataReceived -= Connection_DebugDataReceived; this.Close(); } 

关闭表单会释放Form对象,但不能强制删除其他类对其进行的引用。 当您为事件注册表单时,您基本上是将表单对象引用到事件源(在本例中为SerialPort实例)。

这意味着,即使您的表单已关闭,事件源(您的SerialPort对象)仍在向表单实例发送事件,并且仍在运行处理这些事件的代码。 问题是,当此代码尝试更新已处置的表单(设置其标题,更新其控件,调用InvokeInvoke )时,您将收到此exception。

因此,您需要做的是确保在表单关闭时取消注册事件。 这就像检测表单正在关闭并取消注册Connection_DataReceived事件处理程序一样简单。 通过重写OnFormClosing方法并取消注册该事件,您可以轻松地检测到表单正在关闭:

 protected override OnFormClosing(FormClosingEventArgs args) { Connection.DataReceived -= Connection_DataReceived; } 

我还建议将事件注册移动到OnLoad方法的覆盖,否则它可能会在完全构造表单之前接收事件,这可能会导致令人困惑的exception。

您尚未显示AddReceivedPacketToTextBox方法的代码。

您可以尝试在该方法中检查已处置的表单:

 private void AddReceivedPacketToTextBox(object sender, EventArgs e) { if (this.IsDisposed) return; ... } 

关闭表单时分离DataReceived事件处理程序可能是一个好主意,但还不够:还有一个竞争条件,这意味着可以在关闭/处置表单后调用AddReceivedPacketToTextBox 。 序列将是这样的:

  • 工作线程:触发DataReceived事件,Connection_DataReceived开始执行
  • UI线程:表单已关闭并处理,DataReceived事件已分离。
  • 工作线程:调用Invoke
  • UI线程:在处理表单时执行AddReceivedPacketToTextBox。

我发现如果在按钮事件中单击删除事件,并在该按钮单击中关闭表单,一切都很好,我的调试表关闭,没有任何exception……多么奇怪!

这并不奇怪。 multithreading错误(“Heisenbugs”)与时序相关,这种小的变化会影响时序。 但它不是一个强大的解决方案。

可以通过添加计时器来解决问题:

  bool formClosing = false; private void Connection_DataReceived(object sender, SerialDataReceivedEventArgs e) { if (formClosing) return; _buffer = Connection.ReadExisting(); Invoke(new EventHandler(AddReceivedPacketToTextBox)); } protected override void OnFormClosing(FormClosingEventArgs e) { base.OnFormClosing(e); if (formClosing) return; e.Cancel = true; Timer tmr = new Timer(); tmr.Tick += Tmr_Tick; tmr.Start(); formClosing = true; } void Tmr_Tick(object sender, EventArgs e) { ((Timer)sender).Stop(); this.Close(); } 

感谢MSDN的 JohnWein