5

私には2つのフォームがあります。1つはで、もう1つは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で、シリアルポート接続のイベントを処理するメソッドを追加します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を閉じるとすぐに、LineにがスローObjectDisposedExceptionされInvoke(new EventHandler(AddReceivedPacketToTextBox));ます。

どうすればこれを修正できますか?ヒント/ヘルプは大歓迎です!

アップデート

ボタンのイベントクリックでイベントを削除し、そのボタンのクリックでフォームを閉じると、すべてが正常で、例外なくデバッグフォームが閉じられることがわかりました...なんて奇妙なことでしょう。

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

3 に答える 3

6

フォームを閉じるとFormオブジェクトは破棄されますが、他のクラスがフォームに対して持っている参照を強制的に削除することはできません。フォームをイベントに登録すると、基本的に、フォームオブジェクトへの参照がイベントのソース(SerialPortこの場合はインスタンス)に与えられます。

これは、フォームが閉じていても、イベントソース(SerialPortオブジェクト)がフォームインスタンスにイベントを送信し、これらのイベントを処理するコードが実行されていることを意味します。問題は、このコードが破棄されたフォームを更新しようとすると(タイトルの設定、コントロールの更新、呼び出しInvokeなど)、この例外が発生することです。

したがって、フォームが閉じたときにイベントの登録が解除されるようにする必要があります。これは、フォームが閉じていることを検出してConnection_DataReceivedイベントハンドラーの登録を解除するのと同じくらい簡単です。メソッドをオーバーライドしOnFormClosing、そこでイベントの登録を解除することで、フォームが閉じていることを簡単に検出できます。

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

また、イベント登録をメソッドのオーバーライドに移動することをお勧めします。OnLoadそうしないと、フォームが完全に構築される前にイベントを受信し、混乱を招く例外が発生する可能性があります。

于 2012-10-18T14:25:13.543 に答える
2

AddReceivedPacketToTextBoxメソッドのコードは表示されていません。

その方法で破棄されたフォームをチェックしてみることができます。

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

    ...
}

フォームを閉じるときにイベントハンドラーを切り離すことDataReceivedはおそらく良い考えですが、十分ではありませんAddReceivedPacketToTextBox。フォームが閉じられた/破棄された後に呼び出すことができることを意味する競合状態がまだあります。シーケンスは次のようになります。

  • ワーカースレッド:DataReceivedイベントが発生し、Connection_DataReceivedが実行を開始します
  • UIスレッド:フォームを閉じて破棄し、DataReceivedイベントを切り離しました。
  • ワーカースレッド:Invokeを呼び出します
  • UIスレッド:フォームの破棄中にAddReceivedPacketToTextBoxが実行されました。

ボタンのイベントクリックでイベントを削除し、そのボタンのクリックでフォームを閉じると、すべてが正常で、例外なくデバッグフォームが閉じられることがわかりました...なんて奇妙なことでしょう。

それは奇妙なことではありません。マルチスレッドのバグ(「Heisenbugs」)はタイミングに関連しており、そのような小さな変更がタイミングに影響を与える可能性があります。しかし、それは堅牢なソリューションではありません。

于 2012-10-20T14:47:58.510 に答える
0

この問題は、タイマーを追加することで解決できます。

  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に感謝します

于 2012-10-18T19:30:45.637 に答える