web-dev-qa-db-ja.com

クロススレッドのWinFormイベント処理でInvoke / BeginInvokeの問題を回避しますか?

WinForm UIでのバックグラウンドスレッド処理にまだ悩まされています。どうして?ここにいくつかの問題があります:

  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以上で動作する必要があります

それで、上記の制約があればこれを解決できますか?私は数え切れないほどのブログやディスカッションを検索し、掘り起こしてきました。

更新:この質問には簡単な答えがないことを理解しています。私はこのサイトに数日間しかアクセスしておらず、質問に答える経験が豊富な人を見かけました。これらの個人の1人がこれを十分に解決して、私が1週間も費やさないようにすることで、適切なソリューションを構築するのにかかることを願っています。

アップデート#2:わかりました。問題をもう少し詳しく説明して、何が(もしあれば)どのような影響があるかを確認します。状態を判断できる次のプロパティには、いくつかの懸念があります...

  1. Control.InvokeRequired =現在のスレッドで実行されている場合、またはすべての親に対してIsHandleCreatedがfalseを返した場合にfalseを返すことが記載されています。 InvokeRequired実装がObjectDisposedExceptionをスローするか、オブジェクトのハンドルを再作成する可能性さえあることに困っています。また、InvokeRequiredは、呼び出しができない場合(Dispose in progress)にtrueを返すことができ、呼び出し(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イベントをサブスクライブし、IsDisposedプロパティをチェックして、BeginInvokeが完了するかどうかを判断することを検討しています。ここでの大きな問題は、Dispose-> 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が、待機中のすべてのクロススレッド呼び出しにディスパッチされていることがわかります。これのすぐ後にはthis.window.DestroyHandle()の呼び出しがあり、これによりウィンドウが破棄され、そのハンドルの参照がIntPtr.Zeroに設定されるため、BeginInvokeメソッド(より正確には、BeginInvokeとInvokeの両方を処理するMarshaledInvoke)が呼び出されなくなります。ここでの問題は、threadCallbackListでロックが解放された後、コントロールのスレッドがウィンドウハンドルをゼロにする前に新しいエントリを挿入できることです。これは私が目にしているケースのようですが、まれにですが、リリースを停止するのに十分な場合がよくあります。

アップデート#4:

これをドラッグし続けてすみません。しかし、ここで文書化する価値があると思いました。私は上記の問題のほとんどをなんとか解決し、うまくいく解決策を絞り込んでいます。私が心配していたもう1つの問題にぶつかりましたが、これまで「実況」を見たことはありません。

この問題は、Control.Handleプロパティを記述した天才に関係しています。

    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;
    }

これ自体はそれほど悪くはありません(変更{}に関する私の意見に関係なく)。ただし、InvokeRequiredプロパティまたはInvoke/BeginInvokeメソッドと組み合わせると問題が発生します。 Invokeの基本的なフローは次のとおりです。

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

ここでの問題は、別のスレッドから最初のifステートメントを正常に通過できることです。その後、ハンドルがコントロールのスレッドによって破棄され、Handleプロパティの取得によってスレッドにウィンドウハンドルが再作成されます。これにより、元のコントロールのスレッドで例外が発生する可能性があります。これを防ぐ方法がないので、これは本当に困惑しています。彼らがInternalHandleプロパティのみを使用し、IntPtr.Zeroの結果をテストした場合、これは問題にはなりません。

48
csharptest.net

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

22
Pavel Minaev

しばらく前にこの問題に遭遇し、同期コンテキストを含むソリューションを思いつきました。解決策は、特定のデリゲートをSynchronizationContextがバインドされているスレッドにバインドする拡張メソッドをSynchronizationContextに追加することです。これは、新しいデリゲートを生成します。これは、呼び出されると、appropraiteスレッドへの呼び出しをマーシャリングしてから、元のデリゲートを呼び出します。デリゲートのコンシューマーが間違ったコンテキストでそれを呼び出すことはほとんど不可能になります。

件名に関するブログ投稿:

8
JaredPar

はい、数日後、ソリューションの作成が完了しました。最初の投稿でリストされたすべての制約と目的を解決します。使い方はシンプルで簡単です:

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

ワーカースレッドがこのイベントを呼び出すと、コントロールスレッドへの必要な呼び出しが処理されます。これは、制御スレッドで実行できない場合に、無期限にハングアップすることはなく、常にObjectDisposedExceptionをスローします。エラーを無視するクラスと、コントロールが使用できない場合にデリゲートを直接呼び出すクラスの、他の派生クラスを作成しました。うまく機能しているように見え、上記の問題を再現するいくつかのテストに完全に合格しています。上記の制約#3に違反しない限り回避できないソリューションには1つの問題しかありません。この問題は、問題の説明の最後(更新#4)であり、get Handleのスレッド化の問題です。これにより、元のコントロールスレッドで予期しない動作が発生する可能性があり、Dispose()の呼び出し中にInvalidOperationException()がスローされるのを定期的に確認しました。これに対処するために、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; } }
    }
}

ここで何か問題がある場合は、お知らせください。

7
csharptest.net

私はあなたのすべての要件を満たす包括的なソリューションを書くつもりはありませんが、展望を提供します。全体的に、しかし、私はあなたがそれらの要件で月のために撮影していると思います。

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

2
Adam Robinson

わあ、長い質問。私は私の答えを整理しようと思いますので、もし私が何か間違ったことを理解した場合、あなたが私を訂正できるようにします。

1)異なるスレッドから直接UIメソッドを呼び出す十分な理由がない限り、しないでください。イベントハンドラーを使用して、いつでもプロデューサー/コンシューマーモデルにアクセスできます。

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

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

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

必要なすべての情報をイベント引数で渡すことができます:進行状況の更新、ウィンドウのクローズなど。

2)上記で提案したモデルを使用する場合、InvokeRequiredは必要ありません。この例では、myHandlerを起動しているのは、たとえば、別のスレッドにあるコンポーネントだけであることを知っています。

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

したがって、常にinvokeを使用して、正しいスレッドにいることを確認できます。

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

おっと、長い答え。万が一私があなたの要件を逃したり、あなたが言ったことを誤解した場合(英語は私の母国語ではないので、私たちは確信が持てません)、私に知らせてください。

1
diogoriba

これはかなり難しい質問です。コメントで述べたように、文書化された制約を考えると解決できないと思います。 .netフレームワークの特定の実装を前提としてハッキングできます。さまざまなメンバー関数の実装を知っていると、あちこちでロックを取得し、「実際には別のスレッドで他のメンバー関数を呼び出すのは大丈夫です。 」

だから、今の私の基本的な答えは「いいえ」です。私は.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;

見逃した場合はお知らせください。

1

私は、GUIへのそのようなすべての呼び出しメッセージを、fire and forgetとして整理しようとします(フォームを破棄する際の競合状態のためにGUIがスローできる例外を処理します)。

この方法では、実行されても害はありません。

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

1
Chris Chilvers

これは質問の2番目の部分に対する答えではありませんが、参考のために含めます。

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での最も一般的な落とし穴を回避し、簡単に使用できます。回すだけ

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

control.SafeInvoke(...)

BeginInvokeでも同様の構成が可能です。

1
Filip Navara

これが私が現在使用しているものです。これは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);
      }
   }
0
RenniePet

ユーザーがダイアログを閉じたときにダイアログを非表示にしないのはなぜですか?ダイアログをモーダルに表示しない場合、これは問題なく機能するはずです。 (showdialogの代わりにshowを使用してください)。 showを呼び出すときにホストをダイアログに渡すことで、(必要に応じて)所有しているウィンドウの上に進行ダイアログを保持できると思います。

0
JMarsch

BackgroundWorkerのようにSystem.ComponentModel.ISynchronizeInvokeを作成する場合は、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
0
AMissico

(@Pavelで説明されているように)BackgroundWokerが気に入らない場合は、このライブラリ http://www.wintellect.com/PowerThreading.aspx を確認することをお勧めします。

0
Kane

これを理解したら、アプリケーションの実行中に進捗ダイアログを破棄する必要があるのはなぜですか?ユーザーの要求に応じて単に表示および非表示にしないのはなぜですか?これにより、問題が少なくとも少し簡単になるようです。

0
alexD