2009-09-01 11 views
46

私はまだWinForm UIのバックグラウンドスレッディングに悩まされています。どうして?ここにいくつかの問題があります:クロススレッドWinFormイベント処理でInvoke/BeginInvokeの不具合を回避するには?

  1. 私がそれを作成したのと同じスレッドで実行していない限り、明らかに最も重要な問題は、コントロールを変更できません。
  2. あなたが知っているように、Invoke、BeginInvokeなどは、コントロールが作成されるまで利用できません。
  3. RequiresInvokeがtrueを返した後でも、BeginInvokeはObjectDisposedをスローすることができ、スローしなくても、コントロールが破棄されてもコードを実行することはありません。
  4. RequiresInvokeがtrueを返した後であっても、InvokeはInvokeの呼び出しと同時に破棄されたコントロールによって実行待ちの状態で無期限にハングアップする可能性があります。

私はこの問題の洗練された解決策を探していますが、私が探しているものの詳細に入る前に、問題を明確にすると思っていました。これは、一般的な問題を取り上げ、それの後ろにもっと具体的な例を置くことです。この例では、大量のデータをインターネット経由で転送しています。ユーザーインターフェイスは、すでに進行中の転送の進行状況ダイアログを表示できる必要があります。進行状況のダイアログは、常に迅速に更新されるはずです(毎秒5〜20回更新)。ユーザーはいつでも進行状況ダイアログを閉じることができ、必要に応じて再度呼び出すことができます。さらに、議論が見える場合は、すべての進捗イベントを処理しなければならないという主張ができます。ユーザーは進行状況ダイアログでキャンセルをクリックし、イベント引数を変更して操作を取り消すことができます。

は、今私は、制約の次のボックスに収まるソリューションを必要とする:

  1. ワーカースレッドが実行が完了するまで待つ/コントロール/フォームとブロックのメソッドを呼び出すことができます。
  2. ダイアログ自体が初期化時などにこの同じメソッドを呼び出すことを許可します(したがってinvokeを使用しません)。
  3. ハンドリングメソッドまたは呼び出しイベントに負担をかけることなく、ソリューションはイベントサブスクリプション自体のみを変更する必要があります。
  4. 適切に処理をブロックすると、処理中のダイアログへの呼び出しが呼び出されます。残念ながら、これはIsDisposedをチェックするほど簡単ではありません。
  5. 任意のイベントタイプで使用できる必要があります(EventHandlerタイプの代理人を想定)。
  6. 例外をTargetInvocationExceptionに変換してはいけません。
  7. ソリューションは、上記の.Net 2.0以降

ので、これを解決することができます与えられた制約と協力しなければなりませんか?私は数え切れないほどのブログやディスカッションを通して捜して掘り下げました。私はまだ空手です。

更新:私はこの質問には簡単な答えがないことを認識しています。私は数日間このサイトにいましたが、私は質問に答える多くの経験を持つ人を見ました。私はこれらの個人の一人がこれを十分に解決して週を過ごすことができないようにしています。そうすれば合理的な解決法を構築することができます。

更新#2:はい、問題をもう少し詳しく説明し、何か(何かがあれば)が揺れているかどうかを確認します。私たちが状態を判断できるようにする以下のプロパティは、いくつか懸念を引き起こします。現在のスレッド上またはIsHandleCreatedは、すべての親のためにfalseを返す場合、実行している場合はfalseを返すように文書化

  1. Control.InvokeRequired =。 私は、ObjectDisposedExceptionをスローする可能性があるInvokeRequired実装、またはオブジェクトのハンドルを潜在的に再作成する可能性があることに悩まされています。 InvokeRequiredは呼び出すことができない(Disposeが進行中)ときにtrueを返し、invoke(Create in progress)を使用する必要がある場合でもfalseを返すことができるので、これはすべてのケースで単純に信頼できません。 InvokeRequiredがfalseを返すことがわかる唯一のケースは、IsHandleCreatedが呼び出しの前と後の両方でtrueを返すときです(InvokeRequiredのMSDNドキュメントではIsHandleCreatedのチェックを参照してください)。

  2. Control.IsHandleCreated =ハンドルがコントロールに割り当てられている場合はtrueを返します。それ以外の場合はfalseです。 IsHandleCreatedは安全な呼び出しですが、コントロールがハンドルを再作成している場合には、それが故障する可能性があります。この潜在的な問題は、IsHandleCreatedおよびInvokeRequiredにアクセスしている間にロック(制御)を実行することで解決できるように見えます。

  3. Control.Disposing =コントロールが処理中である場合はtrueを返します。

  4. Control.IsDisposed =コントロールが破棄されている場合はtrueを返します。 私はDisposedイベントを購読し、BeginInvokeが完了するかどうかを判断するためにIsDisposedプロパティをチェックすることを検討しています。ここでの大きな問題は、Disposing - > Disposedトランジションの間に同期化ロックがないことです。 Disposedイベントを購読した後、Disposing == false & & IsDisposed == falseの場合は、Disposedイベントの発生を確認できない可能性があります。これは、Disposeの実装がDisposing = falseを設定し、Disposed = trueを設定するためです。これにより、処分されたコントロールに対してDisposingとIsDisposedの両方をfalseとして読み取ることができます(ただし小さい)。

...頭が痛い:(うまくいけば、上記の情報は、誰もがこれらのトラブルを持つために問題にもう少し光を当てるだろう。私はこれであなたの予備の思考サイクルに感謝。に迫っ

トラブル...次はControl.DestroyHandle()メソッドの後半である:

if (!this.RecreatingHandle && (this.threadCallbackList != null)) 
{ 
    lock (this.threadCallbackList) 
    { 
     Exception exception = new ObjectDisposedException(base.GetType().Name); 
     while (this.threadCallbackList.Count > 0) 
     { 
      ThreadMethodEntry entry = (ThreadMethodEntry) this.threadCallbackList.Dequeue(); 
      entry.exception = exception; 
      entry.Complete(); 
     } 
    } 
} 
if ((0x40 & ((int) ((long) UnsafeNativeMethods.GetWindowLong(new HandleRef(this.window, this.InternalHandle), -20)))) != 0) 
{ 
    UnsafeNativeMethods.DefMDIChildProc(this.InternalHandle, 0x10, IntPtr.Zero, IntPtr.Zero); 
} 
else 
{ 
    this.window.DestroyHandle(); 
} 

あなたは説明ObjectDisposedExceptionが待機中のすべてのクロススレッド呼び出しに派遣されて気づく間もなく、これはこれへの呼び出しで次のよう。 .window.DestroyHandle()は、ウィンドウとseを順番に破棄します。これはIntPtr.Zeroへのハンドル参照で、BeginInvokeメソッド(またはBeginInvokeとInvokeの両方を処理するMarshaledInvoke)へのさらなる呼び出しを防ぎます。ここでの問題は、threadCallbackListのロックが解放された後で、コントロールのスレッドがウィンドウハンドルを0にする前に新しいエントリを挿入できることです。これは、まれにしかリリースを停止するのに十分な頻度で頻繁に見られているようです。

更新#4:

残念ですが、このままドラッグしてください。しかし、私はそれがここで文書化する価値があると思った。私は上記の問題のほとんどを解決することができましたし、私はその解決策に絞っています。私は懸念していたもう一つの問題にぶつかったが、今までは、「野生のもの」は見ていない。

この問題は、コントロールを作成した天才と関係があります。ハンドルのプロパティ:

public IntPtr get_Handle() 
    { 
     if ((checkForIllegalCrossThreadCalls && !inCrossThreadSafeCall) && this.InvokeRequired) 
     { 
      throw new InvalidOperationException(SR.GetString("IllegalCrossThreadCall", new object[] { this.Name })); 
     } 
     if (!this.IsHandleCreated) 
     { 
      this.CreateHandle(); 
     } 
     return this.HandleInternal; 
    } 

これだけでは(get {}の変更に関する私の意見に関係なく)それほど悪くありません。ただし、InvokeRequiredプロパティまたはInvoke/BeginInvokeメソッドと組み合わされた場合、それは悪いことです。ここでは基本的な流れは、Invokeです:

if(!this.IsHandleCreated) 
    throw; 
... do more stuff 
PostMessage(this.Handle, ...); 

ここでの問題は、別のスレッドから、私は成功したようにGETを引き起こし、ハンドルがコントロールのスレッドによって破壊された後、最初のif文を通過することができるということです私のスレッドでウィンドウハンドルを再作成するためのプロパティを処理します。これにより、元のコントロールのスレッドで例外が発生する可能性があります。このことを守る方法がないので、私は本当に私を困惑させています。 InternalHandleプロパティのみを使用し、IntPtr.Zeroの結果をテストした場合、これは問題にはなりません。

+0

質問をしているうちにちょっと丁寧になります。 –

+1

LOL、Ok ...私はそれをきれいにするでしょう。 –

答えて

22

あなたのシナリオは、説明したように、きちんとBackgroundWorkerに合っています - なぜそれを使用しないのですか?ソリューションの要件はあまりにも一般的であり、むしろ不合理なものです。それらをすべて満足させる解決策はないのではないでしょうか。

+0

私はあなたの感情に同意します、これは難しい問題です。私は多くの人々が直面していると確信しています。私がこの疑問を提起するのは、私は解決策があるとは思わないということです。しかし、私は誰かが私を間違っていると証明することを望んでいる。 –

+0

Pavel、BackgroundWorkerで私を案内してくれてありがとう、私はそれが存在するのを知らなかった。それは私が記述したシナリオに美しくフィットしています。 –

+0

これはエレガントではありませんが、私が投稿したソリューションは、Invoke、BeginInvoke、およびBackgroundWorkerが生成するスレッドの問題を抱えずに動作することができます。フォームが完了する前に閉じず、フォームがハンドルを再作成しないことが確実であれば、BackgroundWorkerは依然としてうまく機能します。 –

2

すべての要件を満たす徹底的なソリューションは作成しませんが、私は視点を提供します。しかし全体的に、私はあなたがそれらの要件を持つ月のために撮影していると思います。

Invoke/BeginInvokeアーキテクチャは、コントロールのUIスレッドで提供されたデリゲートをWindowsメッセージを送信するだけで実行し、メッセージループ自体がデリゲートを実行します。これの具体的な動作は無関係ですが、UIスレッドとのスレッド同期のためにこのアーキテクチャを使用する必要は特にありません。​​などのループ実行中にデリゲートが実行するようにQueueを監視して実行するなど、ループ実行が必要です。 InvokeBeginInvokeが提供していないことが具体的にはわかりませんが、自分で実装するのはかなり簡単です。

+0

私は完全に同意しています。これはほとんどのもの(単純なプロデューサ/コンシューマキュー)に対する私の好みです。このソリューションの私の問題は2つあります。 A)クライアントコード(winformで実行されているコード)にかなりの量の影響が必要です。 B)ブロッキング/双方向イベントを容易には許さない。もちろん、あなたはそれを行うことができます、待機ハンドルを作成し、消費者がメッセージを処理するのを待ってから、続行してください。しかし、これは、あなたが開始したのと同じボートに戻ってきます。ユーザーが配置しているUIの可能性に対処します。 –

+0

あるレベルでは、それを残しておくつもりです、良いか悪いか。他の人が示唆しているように、ユーザーが閉じるときに進捗ダイアログを実際に破棄する必要はありません。 –

0

私がこれを理解している場合、なぜアプリケーションが動作している間に進捗ダイアログを破棄する必要がありますか?なぜユーザーの要求に表示して非表示にするのはなぜですか?これは少なくともあなたの問題をより簡単にするように思えます。

+0

このシナリオでは、プロセスが完了するまで単にダイアログを非表示にすると主張できます。しかし、私は一般的なWinForms開発で直面しているより広範な問題について議論する一般的な手段としてこれを使用しています。あなたはこの特定の問題のためにスポットライトを当てますが、すべての状況にプリンシパルを適用することはできません。 –

+0

これは良い見解ですが、アプリケーションの必然的なシャットダウンフェーズに照らせば失敗します。アプリケーションは最終的にシャットダウンする必要があり、そのハンドルを処分する必要がありますし、他のコントロールがあなたのフォームを呼び出すことがあります。フォームを非表示にすると、最後まで問題が隠されます。 –

1

これは本当に質問の後半部分に答えていないが、私はちょうど参照用に含まれます:

private delegate object SafeInvokeCallback(Control control, Delegate method, params object[] parameters); 
public static object SafeInvoke(this Control control, Delegate method, params object[] parameters) 
{ 
    if (control == null) 
     throw new ArgumentNullException("control"); 
    if (control.InvokeRequired) 
    { 
     IAsyncResult result = null; 
     try { result = control.BeginInvoke(new SafeInvokeCallback(SafeInvoke), control, method, parameters); } 
     catch (InvalidOperationException) { /* This control has not been created or was already (more likely) closed. */ } 
     if (result != null) 
      return control.EndInvoke(result); 
    } 
    else 
    { 
     if (!control.IsDisposed) 
      return method.DynamicInvoke(parameters); 
    } 
    return null; 
} 

このコードは、Invoke/BeginInvokeを持つ最も一般的な落とし穴を避ける必要があり、それがするのは簡単ですつかいます 。ただ、同様の構築物はBeginInvokeメソッドのために可能である

control.SafeInvoke(...) 

if (control.InvokeRequired) 
    control.Invoke(...) 
else 
    ... 

を回します。

+0

上記で述べたように、http://msdn.microsoft.com/en-us/library/0b1bf3y3によれば、コントロールを作成したスレッドとは別のスレッドでは 'IsDisposed'を呼び出すことはできません。実際には、これは解決不可能と思われるので、これは非常に良いハックかもしれません。ただし、ハックであることを覚えておく必要があります(文書化された制限に従わない)。 –

6

[OK]を、後で私は解決策の作成を完了しました。これは、最初の投稿に列挙されているすべての制約と目的を解決します。使用法は単純明快です:

myWorker.SomeEvent += new EventHandlerForControl<EventArgs>(this, myWorker_SomeEvent).EventHandler; 

ワーカースレッドがこのイベントを呼び出すと、コントロールスレッドへの必要な呼び出しを処理します。これは、スレッドが無期限にハングしないようにし、コントロールスレッドで実行できない場合、ObjectDisposedExceptionを一貫してスローします。クラスの他の派生物を作成しました.1つはエラーを無視し、もう1つはコントロールが利用できない場合に直接デリゲートを呼び出します。上記の問題を再現するいくつかのテストに合格しています。上記の制約#3に違反することなく防ぐことができない解決策には、1つの問題しかありません。この問題は、問題の説明の最後のもの(Update#4)です。ハンドルを取得する際のスレッドの問題です。これは、元のコントロールのスレッドで予期しない動作を引き起こす可能性があり、私は定期的にInvalidOperationException()Dispose()呼び出し中にスローされたハンドルを作成しているプロセスでのスレッドです。これに対処するために、Control.Handleプロパティを使用する関数にアクセスするためのロックを確保します。これにより、フォームはDestroyHandleメソッドをオーバーロードし、基本実装を呼び出す前にロックすることができます。これが完了したら、このクラスはスレッドセーフでなければなりません(私の知る限り)。

public class Form : System.Windows.Forms.Form 
{ 
    protected override void DestroyHandle() 
    { 
     lock (this) base.DestroyHandle(); 
    } 
} 

デッドロックの解決の中心的な側面がポーリングループになったことに気付くかもしれません。もともとは、DisposedとHandleDestroyedのコントロールのイベントを処理し、複数の待機ハンドルを使用してテストケースを解決しました。もっと慎重に検討した結果、これらのイベントからのサブスクリプション/サブスクリプションがスレッドセーフではないことがわかりました。したがって、スレッドのイベントに不必要な競合を作成しないように、デッドロック状態を発生させる可能性を回避するため、IsHandleCreatedをポーリングすることを選択しました。

とにかく、ここで私が思い付いたソリューションです:あなたはここに何か問題が表示された場合、私に知らせてください

/// <summary> 
/// Provies a wrapper type around event handlers for a control that are safe to be 
/// used from events on another thread. If the control is not valid at the time the 
/// delegate is called an exception of type ObjectDisposedExcpetion will be raised. 
/// </summary> 
[System.Diagnostics.DebuggerNonUserCode] 
public class EventHandlerForControl<TEventArgs> where TEventArgs : EventArgs 
{ 
    /// <summary> The control who's thread we will use for the invoke </summary> 
    protected readonly Control _control; 
    /// <summary> The delegate to invoke on the control </summary> 
    protected readonly EventHandler<TEventArgs> _delegate; 

    /// <summary> 
    /// Constructs an EventHandler for the specified method on the given control instance. 
    /// </summary> 
    public EventHandlerForControl(Control control, EventHandler<TEventArgs> handler) 
    { 
     if (control == null) throw new ArgumentNullException("control"); 
     _control = control.TopLevelControl; 
     if (handler == null) throw new ArgumentNullException("handler"); 
     _delegate = handler; 
    } 

    /// <summary> 
    /// Constructs an EventHandler for the specified delegate converting it to the expected 
    /// EventHandler&lt;TEventArgs> delegate type. 
    /// </summary> 
    public EventHandlerForControl(Control control, Delegate handler) 
    { 
     if (control == null) throw new ArgumentNullException("control"); 
     _control = control.TopLevelControl; 
     if (handler == null) throw new ArgumentNullException("handler"); 

     //_delegate = handler.Convert<EventHandler<TEventArgs>>(); 
     _delegate = handler as EventHandler<TEventArgs>; 
     if (_delegate == null) 
     { 
      foreach (Delegate d in handler.GetInvocationList()) 
      { 
       _delegate = (EventHandler<TEventArgs>) Delegate.Combine(_delegate, 
        Delegate.CreateDelegate(typeof(EventHandler<TEventArgs>), d.Target, d.Method, true) 
       ); 
      } 
     } 
     if (_delegate == null) throw new ArgumentNullException("_delegate"); 
    } 


    /// <summary> 
    /// Used to handle the condition that a control's handle is not currently available. This 
    /// can either be before construction or after being disposed. 
    /// </summary> 
    protected virtual void OnControlDisposed(object sender, TEventArgs args) 
    { 
     throw new ObjectDisposedException(_control.GetType().Name); 
    } 

    /// <summary> 
    /// This object will allow an implicit cast to the EventHandler&lt;T> type for easier use. 
    /// </summary> 
    public static implicit operator EventHandler<TEventArgs>(EventHandlerForControl<TEventArgs> instance) 
    { return instance.EventHandler; } 

    /// <summary> 
    /// Handles the 'magic' of safely invoking the delegate on the control without producing 
    /// a dead-lock. 
    /// </summary> 
    public void EventHandler(object sender, TEventArgs args) 
    { 
     bool requiresInvoke = false, hasHandle = false; 
     try 
     { 
      lock (_control) // locked to avoid conflicts with RecreateHandle and DestroyHandle 
      { 
       if (true == (hasHandle = _control.IsHandleCreated)) 
       { 
        requiresInvoke = _control.InvokeRequired; 
        // must remain true for InvokeRequired to be dependable 
        hasHandle &= _control.IsHandleCreated; 
       } 
      } 
     } 
     catch (ObjectDisposedException) 
     { 
      requiresInvoke = hasHandle = false; 
     } 

     if (!requiresInvoke && hasHandle) // control is from the current thread 
     { 
      _delegate(sender, args); 
      return; 
     } 
     else if (hasHandle) // control invoke *might* work 
     { 
      MethodInvokerImpl invocation = new MethodInvokerImpl(_delegate, sender, args); 
      IAsyncResult result = null; 
      try 
      { 
       lock (_control)// locked to avoid conflicts with RecreateHandle and DestroyHandle 
        result = _control.BeginInvoke(invocation.Invoker); 
      } 
      catch (InvalidOperationException) 
      { } 

      try 
      { 
       if (result != null) 
       { 
        WaitHandle handle = result.AsyncWaitHandle; 
        TimeSpan interval = TimeSpan.FromSeconds(1); 
        bool complete = false; 

        while (!complete && (invocation.MethodRunning || _control.IsHandleCreated)) 
        { 
         if (invocation.MethodRunning) 
          complete = handle.WaitOne();//no need to continue polling once running 
         else 
          complete = handle.WaitOne(interval); 
        } 

        if (complete) 
        { 
         _control.EndInvoke(result); 
         return; 
        } 
       } 
      } 
      catch (ObjectDisposedException ode) 
      { 
       if (ode.ObjectName != _control.GetType().Name) 
        throw;// *likely* from some other source... 
      } 
     } 

     OnControlDisposed(sender, args); 
    } 

    /// <summary> 
    /// The class is used to take advantage of a special-case in the Control.InvokeMarshaledCallbackDo() 
    /// implementation that allows us to preserve the exception types that are thrown rather than doing 
    /// a delegate.DynamicInvoke(); 
    /// </summary> 
    [System.Diagnostics.DebuggerNonUserCode] 
    private class MethodInvokerImpl 
    { 
     readonly EventHandler<TEventArgs> _handler; 
     readonly object _sender; 
     readonly TEventArgs _args; 
     private bool _received; 

     public MethodInvokerImpl(EventHandler<TEventArgs> handler, object sender, TEventArgs args) 
     { 
      _received = false; 
      _handler = handler; 
      _sender = sender; 
      _args = args; 
     } 

     public MethodInvoker Invoker { get { return this.Invoke; } } 
     private void Invoke() { _received = true; _handler(_sender, _args); } 

     public bool MethodRunning { get { return _received; } } 
    } 
} 

が。

+0

誰かがより良い解決策を持っていますか?私は数日間賞品を掲示し、何が現れてくるか見るでしょう。 –

+0

まだ興味があるなら、これをチェックしてください:http://stackoverflow.com/questions/4190299/exploiting-the-backgroundworker-for-cross-thread-invocation-of-gui-actions-on-win –

+0

MSDNからの最も重要な読書を見逃してしまった:「InvokeRequired'プロパティに加えて、スレッドセーフであるコントロールには、 'Invoke'、' BeginInvoke'、 'EndInvoke'、コントロールのハンドルが既に作成されている場合は 'CreateGraphics'があります。コントロールのハンドルがバックグラウンドスレッドで作成される前に' CreateGraphics'を呼び出すと、不正なクロススレッドコールが発生する可能性があります。 –

0

なぜユーザーがダイアログを閉じるときにダイアログを非表示にするのはなぜですか?そのダイアログをモーダルで表示しないとうまくいくはずです。 (showdialogではなくshowを使用してください)。私はあなたがショーを呼び出すときにダイアログにホストを渡すことによってあなたの所有ウィンドウの上にあなたの進捗ダイアログを保持することができると信じています。

+0

ダイアログを非表示にすると、問題が隠されます(私のalexDへのコメントを参照)。 –

+0

申し訳ありません - 多分私は何かを逃しています。それは他のプロセスが所有するものを参照しますか?プロセスがシャットダウンすると、そのプロセスが所有するすべてのリソースが破棄されます。私はあなたが何らかのアウトオブプロセスリソースを保持している場合にのみ問題があると思います。 – JMarsch

+0

私が参照しているハンドルは、 'Control.Handle'以外ではありません。ですから、問題は最終的にはダイアログを 'Close()'する必要があり、 'BeginInvoke'へのリクエストを受け取り、正常に応答する必要があるということです。 –

1

うわー、長い質問です。 私は自分の答えを整理しようと思うので、何か間違ったことを理解していれば訂正することができます。

1)別のスレッドからUIメソッドを直接呼び出す理由がない限り、そうしないでください。あなたは、常にイベントハンドラを使用してプロデューサ/コンシューマモデルのために行くことができます。

protected override void OnLoad() 
{ 
    //... 
    component.Event += new EventHandler(myHandler); 
} 

protected override void OnClosing() 
{ 
    //... 
    component.Event -= new EventHandler(myHandler); 
} 

はmyHandlerは、別のスレッド内のコンポーネントは、例えば、UIで何かを実行する必要があるたびに起動されます。 OnLoadでイベントハンドラを設定し、OnClosingでサブスクライブを解除すると、ハンドルが作成され、イベントを処理する準備ができている間だけ、イベントがUIで受信/処理されるようになります。あなたがイベントの購読をやめることはないので、処理中であれば、このダイアログにイベントを発行することさえできません。 1つのイベントがまだ処理されている間に別のイベントが発生すると、そのイベントはキューに入れられます。

あなたはイベント引数に必要なすべての情報渡すことができます。

2など、ウィンドウを閉じて、あなたは進捗状況を更新しているかどうかを)あなたは、私が提案したモデルを使用する場合は、InvokeRequiredは必要ありません。上記。この例では、myHandlerを起動する唯一のものは、たとえば別のスレッドに存在するコンポーネントです。

private void myHandler(object sender, EventArgs args) 
{ 
    BeginInvoke(Action(myMethod)); 
} 

これで、常に正しいスレッドになるように、invokeを使用できます。

3)同期呼び出しには注意してください。必要に応じて、BeginInvokeの代わりにInvokeを使用することを置き換えることができます。これにより、イベントが処理されるまでコンポーネントがブロックされます。しかし、UI内で、コンポーネントが存在するスレッド専用のものと通信する必要がある場合は、デッドロックの問題が発生する可能性があります。 (私は自分自身を明確にしたかどうかわかりません、私に知らせてください)。私は、リフレクション(TargetInvocationException)とBeginInvoke(別の​​スレッドを開始すると、スタックトレースの一部を失う)を使用する際に例外が発生しましたが、Invoke呼び出しで多くの問題を抱えていることを思い出しません。それは例外になると安全です。

Whoa、長い答え。万が一私があなたの要求を見逃したり、あなたが言ったことを誤解した場合(私の母国語ではないので、わからない)、私に知らせてください。

+2

ポイント1は、終了するときにイベントの登録解除を提案しますが、これはスレッドセーフではありません。プリエンプティブマルチスレッドのためにハンドルが破棄される前に、呼び出しリストの「あなたの」存在がなくなっていることを確かめることはできません。あなたのハンドルは廃棄されます。ポイント2の優れた注釈。 –

1

は、私は(フォームを配置することに起因する競合状態にGUIを投げることができる例外処理)火災などGUIにそのようなすべての起動メッセージを整理し、忘れるようにしてみてください。

このようにしても、決して害を及ぼさない場合はこのようにします。

GUIが作業スレッドに応答する必要がある場合、通知を効果的に取り消す方法があります。単純なニーズのために、BackgroundWorkerはこれをすでに処理しています。

8

私はこの問題を少し前に突き止め、同期コンテキストを含む解決策を思いつきました。解決方法は、特定のデリゲートをSynchronizationContextがバインドされているスレッドにバインドする拡張メソッドをSynchronizationContextに追加することです。呼び出されるとappropraiteスレッドへの呼び出しをマーシャリングし、元のデリゲートを呼び出す新しいデリゲートを生成します。それは、デリゲートの消費者が間違ったコンテキストでそれを呼び出すことをほぼ不可能にします。件名に

ブログ記事:System.ComponentModel.ISynchronizeInvokeを使用して

+0

ちょっとした作業で、DynamicInvoke呼び出しを回避してTargetInvocationExceptionを生成しない優れた解決策です。私はすべてこのアプローチが好きですが、私は1つの質問があります:メソッドを生成するオーバーヘッドは何ですか?彼らはもはや使用されていないときにアンロードされますか?さらに、デリゲートをラップする単純なクラスとは対照的に、メソッド生成のアプローチを選択した理由はまだ分かりません。 –

+0

非常に興味深いブログ記事。 1つの小さなニックピッキングポイント: "ISynchronizedInvoke"を5回言います。私はISynchronizeInvokeを意味すると思いますか? – RenniePet

+0

あなたは「デリゲート・インスタンスを即座に作成することは簡単ではありません。デリゲート・シグネチャのクラスにすべてのパーミュテーションをコーディングしない限り、一致するシグネチャをメソッドに提供できないため、Delegate.Create APIは使用できません。ジェネリックAction <>の構築を使用することで、この問題を大幅に減らすことはできませんか?次に、0引数を取るメソッド、1引数を取るメソッド、2引数を取るメソッド、そしておそらくそれをサポートするメソッドをサポートする必要があります。それとも、私は何かを完全に誤解しましたか? – RenniePet

0

などBackgroundWorkerとして、System.ComponentModel.Componentを作成するときにいいです。次のコードスニペットは、FileSystemWaterがイベントを処理する方法です。

''' <summary> 
    ''' Gets or sets the object used to marshal the event handler calls issued as a result of finding a file in a search. 
    ''' </summary> 
    <IODescription(SR.FSS_SynchronizingObject), DefaultValue(CType(Nothing, String))> _ 
    Public Property SynchronizingObject() As System.ComponentModel.ISynchronizeInvoke 
     Get 
      If (_synchronizingObject Is Nothing) AndAlso (MyBase.DesignMode) Then 
       Dim oHost As IDesignerHost = DirectCast(MyBase.GetService(GetType(IDesignerHost)), IDesignerHost) 
       If (Not (oHost Is Nothing)) Then 
        Dim oRootComponent As Object = oHost.RootComponent 
        If (Not (oRootComponent Is Nothing)) AndAlso (TypeOf oRootComponent Is ISynchronizeInvoke) Then 
         _synchronizingObject = DirectCast(oRootComponent, ISynchronizeInvoke) 
        End If 
       End If 
      End If 
      Return _synchronizingObject 
     End Get 
     Set(ByVal Value As System.ComponentModel.ISynchronizeInvoke) 
      _synchronizingObject = Value 
     End Set 
    End Property 

    Private _onStartupHandler As EventHandler 

    Protected Sub OnStartup(ByVal e As EventArgs) 
     If ((Not Me.SynchronizingObject Is Nothing) AndAlso Me.SynchronizingObject.InvokeRequired) Then 
      Me.SynchronizingObject.BeginInvoke(_onStartupHandler, New Object() {Me, e}) 
     Else 
      _onStartupHandler.Invoke(Me, e) 
     End If 
    End Sub 
1

これは非常に難しい質問です。コメントで言及したように、私はそれが文書化された制約を受けて解決できるとは思わない。さまざまなメンバ関数の実装がわかっていると、ここやそこでロックをつかんで、「実際には別のスレッドで他のメンバ関数を呼び出すことができます。 "

私の基本的な答えは「いいえ」です。私は.Netフレームワークに多大な信用を持っているので、これは不可能だと言うことは嫌です。また、私は比較的初心者で、一般的なフレームワークやCSを研究していませんが、インターネットは公開されています(私のような無知の人にも)!

別のトピックでは、引数は「Invokeは必要ありません。BeginInvokeのみ使用してください」とよくサポートされています。私はそれをサポートしようとすることを心配しないであろうし、それが正しいアサーションだと言うことさえあるが、私は共通の実装が間違っていて、働く(私が望む)ものを言うと言うだろう。ここで

は(ここでは異なる答えから取られた)一般的な実装です:

protected override void OnLoad() 
{ 
    //... 
    component.Event += new EventHandler(myHandler); 
} 

protected override void OnClosing() 
{ 
    //... 
    component.Event -= new EventHandler(myHandler); 
} 

これは、スレッドセーフではありません。コンポーネントは、サブスクリプションの直前に呼び出しリストを簡単に呼び出すことができ、処理が終了した後でのみハンドラが呼び出されます。実際のところ、はどのようにが.Netのイベントメカニズムを使用しなければならないのか、正直なところ、彼はあなたに電話をかける必要はありません。あなたの電話番号を与えたら、誰も消去する必要はありませんそれ!

ベターです:

protected override void OnLoad(System.EventArgs e) 
{ 
    component.Event += new System.EventHandler(myHandler); 
}  
protected override void OnFormClosing(FormClosedEventArgs e) 
{ 
    component.Event -= new System.EventHandler(myHandler); 
    lock (lockobj) 
    { 
     closing = true; 
    } 
} 
private void Handler(object a, System.EventArgs e) 
{ 
    lock (lockobj) 
    { 
     if (closing) 
      return; 
     this.BeginInvoke(new System.Action(HandlerImpl)); 
    } 
} 
/*Must be called only on GUI thread*/ 
private void HandlerImpl() 
{ 
    this.Hide(); 
} 
private readonly object lockobj = new object(); 
private volatile bool closing = false; 

私が何かを逃した場合は私に知らせてください。

+0

これはしばらく前に私のために解決されました。http://csharptest.net/browse/src/Library/Delegates/EventHandlerForControl.csあなたがする必要があるのは、ベースを呼び出す前に(この)ロックするDestroyHandle()のオーバーライドとこの投稿への私の答えに示されているように、あなたがイベントを購読する方法を変えてください。 –

+0

@ csharptest.netこれはどうやって知っていますか?それはどこかに文書化されていますか? –

+0

BCLコードを読み、問題を分析し、ソリューションを実装するまでにはしばらく時間がかかりました。これを何年にもわたって使用した後、生産と一連の厳格な自動テストの両方で、私はその動作に非常に自信があります。 ForcedEventHandlerForControl を使用する場合は、イベントハンドラ内のコードに注意する必要があります。ただし、EventHandlerForControl とEventHandlerForActiveControl は、イベントハンドラ内のコードに関係なく安全に使用できます。ほとんどの場合、私はEventHandlerForActiveControl を使用しています。廃棄されたフォームを更新する点がないからです。 –

0

ここに私が現在使っているものがあります。これはSynchronizationContextの使用に基づいており、JaredParのブログ記事に触発されました。これは完璧ではないかもしれませんが、私が経験していたOPの問題のいくつかを回避します。

// Homemade Action-style delegates to provide .Net 2.0 compatibility, since .Net 2.0 does not 
    // include a non-generic Action delegate nor Action delegates with more than one generic type 
    // parameter. (The DMethodWithOneParameter<T> definition is not needed, could be Action<T> 
    // instead, but is defined for consistency.) Some interesting observations can be found here: 
    // http://geekswithblogs.net/BlackRabbitCoder/archive/2011/11/03/c.net-little-wonders-the-generic-action-delegates.aspx 
    public delegate void DMethodWithNoParameters(); 
    public delegate void DMethodWithOneParameter<T>(T parameter1); 
    public delegate void DMethodWithTwoParameters<T1, T2>(T1 parameter1, T2 parameter2); 
    public delegate void DMethodWithThreeParameters<T1, T2, T3>(T1 parameter1, T2 parameter2, T3 parameter3); 


    /// <summary> 
    /// Class containing support code to use the SynchronizationContext mechanism to dispatch the 
    /// execution of a method to the WinForms UI thread, from another thread. This can be used as an 
    /// alternative to the Control.BeginInvoke() mechanism which can be problematic under certain 
    /// conditions. See for example the discussion here: 
    /// http://stackoverflow.com/questions/1364116/avoiding-the-woes-of-invoke-begininvoke-in-cross-thread-winform-event-handling 
    /// 
    /// As currently coded this works with methods that take zero, one, two or three arguments, but 
    /// it is a trivial job to extend the code for methods taking more arguments. 
    /// </summary> 
    public class WinFormsHelper 
    { 
     // An arbitrary WinForms control associated with thread 1, used to check that thread-switching 
     // with the SynchronizationContext mechanism should be OK 
     private readonly Control _thread1Control = null; 

     // SynchronizationContext for the WinForms environment's UI thread 
     private readonly WindowsFormsSynchronizationContext _synchronizationContext; 


     /// <summary> 
     /// Constructor. This must be called on the WinForms UI thread, typically thread 1. (Unless 
     /// running under the Visual Studio debugger, then the thread number is arbitrary.) 
     /// 
     /// The provided "thread 1 control" must be some WinForms control that will remain in 
     /// existence for as long as this object is going to be used, for example the main Form 
     /// control for the application. 
     /// </summary> 
     /// <param name="thread1Control">see above</param> 
     public WinFormsHelper(Control thread1Control) 
     { 
     _thread1Control = thread1Control; 
     if (thread1Control.InvokeRequired) 
      throw new Exception("Not called on thread associated with WinForms controls."); 

     _synchronizationContext = 
          SynchronizationContext.Current as WindowsFormsSynchronizationContext; 
     if (_synchronizationContext == null) // Should not be possible? 
      throw new Exception("SynchronizationContext.Current = null or wrong type."); 
     } 


     // The following BeginInvoke() methods follow a boilerplate pattern for how these methods 
     // should be implemented - they differ only in the number of arguments that the caller wants 
     // to provide. 

     public void BeginInvoke(DMethodWithNoParameters methodWithNoParameters) 
     { 
     _synchronizationContext.Post((object stateNotUsed) => 
     { 
      if (!_thread1Control.IsDisposed) 
       methodWithNoParameters(); 
     }, null); 
     } 


     public void BeginInvoke<T>(DMethodWithOneParameter<T> methodWithOneParameter, T parameter1) 
     { 
     _synchronizationContext.Post((object stateNotUsed) => 
     { 
      if (!_thread1Control.IsDisposed) 
       methodWithOneParameter(parameter1); 
     }, null); 
     } 


     public void BeginInvoke<T1, T2>(DMethodWithTwoParameters<T1, T2> methodWithTwoParameters, 
             T1 parameter1, T2 parameter2) 
     { 
     _synchronizationContext.Post((object stateNotUsed) => 
     { 
      if (!_thread1Control.IsDisposed) 
       methodWithTwoParameters(parameter1, parameter2); 
     }, null); 
     } 


     public void BeginInvoke<T1, T2, T3>(DMethodWithThreeParameters<T1, T2, T3> methodWithThreeParameters, 
              T1 parameter1, T2 parameter2, T3 parameter3) 
     { 
     _synchronizationContext.Post((object stateNotUsed) => 
     { 
      if (!_thread1Control.IsDisposed) 
       methodWithThreeParameters(parameter1, parameter2, parameter3); 
     }, null); 
     } 
    } 
関連する問題