5

他の場所で発生したイベントを「リッスン」するフォームがあります(フォーム自体やその子コントロールの1つではありません)。イベントは、フォームが破棄された後も存在するオブジェクトによって発生し、フォーム ハンドルが作成されたスレッド以外のスレッドで発生する可能性があります。つまり、イベント ハンドラーで Invoke を実行する必要があります (フォームなど)。

(オーバーライドされた) フォームのDispose(bool)メソッドで、このメソッドが呼び出されたときにまだサブスクライブされている可能性のあるすべてのイベントからサブスクライブを解除しました。ただし、Invoke は、イベント ハンドラーの 1 つから呼び出されることがあります。これは、イベントがサブスクライブ解除される直前にイベント ハンドラーが呼び出され、OS が制御を実行する dispose メソッドに切り替えてから、破棄されたオブジェクトで Invoke メソッドを呼び出すハンドラーに制御を戻すためだと思います。

メインスレッドが呼び出されたメソッドを処理するまで、Invoke を呼び出すと呼び出しスレッドがロックされるため、スレッドをロックしても役に立ちません。メイン スレッド自体が、Invoke 呼び出しスレッドが取得したオブジェクトのロックの解放を待機している可能性があり、デッドロックが発生する可能性があるため、これは発生しない可能性があります。

要するに、別のスレッドで発生する可能性のある外部イベントにサブスクライブされているときに、フォームを正しく破棄するにはどうすればよいですか?

現時点でいくつかの主要な方法がどのように見えるかを次に示します。このアプローチは、上で説明した問題に苦しんでいますが、それらを修正する方法がわかりません。

これは、モデルのデータ部分の変更を処理するイベント ハンドラーです。

private void updateData()
{
 if (model != null && model.Data != null)
 {
  model.Data.SomeDataChanged -= new MyEventHandler(updateSomeData);

  model.Data.SomeDataChanged += new MyEventHandler(updateSomeData);
 }
 updateSomeData();
}

これは、ビューに変更を加える必要があるイベント ハンドラーです。

private void updateSomeData()
{
 if (this.InvokeRequired) this.myInvoke(new MethodInvoker(updateSomeData));
 else
 {
  // do the necessary changes
 }
}

そして myInvoke メソッド:

private object myInvoke(Delegate method)
{
 object res = null;
 lock (lockObject)
 {
  if (!this.IsDisposed) res = this.Invoke(method);
 }
 return res;
}

メソッドの私のオーバーライドDispose(bool):

protected override void Dispose(bool disposing)
{
 lock (lockObject)
 {
  if (disposing)
  {
   if (model != null)
   {
    if (model.Data != null)
    {
     model.Data.SomeDataChanged -= new MyEventHandler(updateSomeData);
    }
    // unsubscribe other events, omitted for brevity
   }
   if (components != null)
   {
    components.Dispose();
   }
  }
  base.Dispose(disposing);
 }
}

更新 (アランの要求による):

Dispose メソッドを明示的に呼び出すことはありません。フレームワークによって行われます。これまでのところ、デッドロックはアプリケーションが閉じられたときにのみ発生しました。ロックを行う前に、フォームが単純に閉じられたときにいくつかの例外がスローされることがありました。

4

3 に答える 3

3

考慮すべき 2 つのアプローチがあります。1 つは、 内にロック オブジェクトを持ち、ロック内でFormへの内部呼び出しDisposeBeginInvoke呼び出しが発生するようにすることです。どちらDisposeBeginInvoke非常に長くかかるべきではないため、コードはロックを長く待つ必要はありません。

もう 1 つのアプローチは、Control.BeginInvoke/の設計上の誤りによりForm.BeginInvoke、これらのメソッドが実際には防ぐことができない例外をスローすることがあり、アクションがフォームで発生するかどうかが実際に問題にならない場合は単に飲み込む必要があることを宣言することです。とにかく処分されました。

于 2012-07-05T23:45:06.763 に答える
0

興味深いかもしれないスーパーキャットの答えに一種の補遺を提供したいと思います。

まず、CountdownEvent (_invoke_counter と呼びます) を初期カウント 1 で作成します。これは、フォーム (またはコントロール) 自体のメンバー変数である必要があります。

private readonly CountdownEvent _invoke_counter = new CountdownEvent(1);

Invoke/BeginInvoke の各使用を次のようにラップします。

if(_invoke_counter.TryAddCount())
{
    try
    {
        //code using Invoke/BeginInvoke goes here
    }
    finally { _invoke_counter.Signal(); }
}

次に、 Dispose で次のことができます。

_invoke_counter.Signal();
_invoke_counter.Wait();

これにより、他にもいくつかの優れたことが可能になります。CountdownEvent.Wait() 関数には、タイムアウトのあるオーバーロードがあります。おそらく、呼び出し関数を終了させる前に、一定の時間待機するだけで関数を終了させたい場合があります。Invokes が終了するまでに長い時間がかかることが予想される場合は、DoEvents() を使用してループ内で Wait(100) のようなことを実行して応答性を維持することもできます。この方法で達成できる気の利いたことはたくさんあります。

これにより、奇妙なタイミングの競合状態タイプの問題が回避され、理解と実装が非常に簡単になります。私はこの方法を製品ソフトウェアで使用しているため、これに関して明らかな問題が見られる場合は、ぜひお知らせください。

重要: 破棄コードがファイナライザーのスレッドにあることを確認してください (「自然な」破棄である必要があります)。UI スレッドから手動で Dispose() メソッドを呼び出そうとすると、_invoke_counter.Wait(); でスタックするため、デッドロックが発生します。呼び出しが実行されないなど。

于 2013-07-03T13:13:02.460 に答える