48

UI以外のスレッドからUIコントロールを操作する場合は、問題を回避するためにUIスレッドへの呼び出しをマーシャリングする必要があることを私は知っています。一般的なコンセンサスは、test InvokeRequiredを使用する必要があり、trueの場合は、.Invokeを使用してマーシャリングを実行することです。

これにより、次のような多くのコードが作成されます。

private void UpdateSummary(string text)
{
    if (this.InvokeRequired)
    {
        this.Invoke(new Action(() => UpdateSummary(text)));
    }
    else
    {
        summary.Text = text;
    }
}

私の質問はこれです:InvokeRequiredテストを省略して、次のようにInvokeを呼び出すことはできますか?

private void UpdateSummary(string text)
{
    this.Invoke(new Action(() => summary.Text = text));
}

これを行うことに問題がありますか?もしそうなら、このパターンをあちこちにコピーして貼り付ける必要がなく、InvokeRequiredテストを維持するためのより良い方法はありますか?

4

8 に答える 8

66

さてこれはどうですか:

public static class ControlHelpers
{
    public static void InvokeIfRequired<T>(this T control, Action<T> action) where T : ISynchronizeInvoke
    {
        if (control.InvokeRequired)
        {
            control.Invoke(new Action(() => action(control)), null);
        }
        else
        {
            action(control);
        }
    }
}

次のように使用します。

private void UpdateSummary(string text)
{
    summary.InvokeIfRequired(s => { s.Text = text });
}
于 2010-10-06T15:29:52.283 に答える
8

InvokeUIスレッドからの呼び出しはやや非効率的です。

代わりに、パラメーターInvokeIfNeededを受け取る拡張メソッドを作成できActionます。(これにより、コールサイトから削除することもできますnew Action(...)

于 2010-10-06T15:29:45.787 に答える
8

UIスレッドではなく、UIスレッド自体ではなく、呼び出しをIFFで使用する必要があるかどうかを確認するために、ロジックチェックを追加する際の引数について前後に読んでいます。さまざまなメソッドの実行時間を ( Stopwatchを介して) 調べて、あるメソッドの効率を別のメソッドよりも大まかに見積もるクラスを作成しました。

結果は驚くかもしれません (これらのテストはForm.Shownイベントを介して実行されました)。

     // notice that we are updating the form's title bar 10,000 times
     // directly on the UI thread
     TimedAction.Go
     (
        "Direct on UI Thread",
        () =>
        {
           for (int i = 0; i < 10000; i++)
           {
              this.Text = "1234567890";
           }
        }
     );

     // notice that we are invoking the update of the title bar
     // (UI thread -> [invoke] -> UI thread)
     TimedAction.Go
     (
        "Invoke on UI Thread",
        () =>
        {
           this.Invoke
           (
              new Action
              (
                 () =>
                 {
                    for (int i = 0; i < 10000; i++)
                    {
                       this.Text = "1234567890";
                    }
                 }
              )
           );
        }
     );

     // the following is invoking each UPDATE on the UI thread from the UI thread
     // (10,000 invokes)
     TimedAction.Go
     (
        "Separate Invoke on UI Thread",
        () =>
        {
           for (int i = 0; i < 10000; i++)
           {
              this.Invoke
              (
                 new Action
                 (
                    () =>
                    {
                       this.Text = "1234567890";
                    }
                 )
              );
           }
        }
     );

結果は次のとおりです。

  • TimedAction::Go()+0 - デバッグ: [DEBUG] ストップウォッチ [UI スレッドで直接]: 300ms
  • TimedAction::Go()+0 - デバッグ: [DEBUG] ストップウォッチ [UI スレッドで呼び出し]: 299ms
  • TimedAction::Go()+0 - デバッグ: [DEBUG] ストップウォッチ [UI スレッドでの個別呼び出し]: 649ms

私の結論は、UI スレッド上かワーカー スレッド上かに関係なく、メッセージ ポンプを介してループバックする大きなオーバーヘッドなしで、いつでも安全に呼び出すことができるということです。ただし、( Invoke()を介して) UI スレッドに多くの呼び出しを行う代わりに、ほとんどの作業を UI スレッドで実行することは有利であり、効率が大幅に向上します。

于 2010-10-06T18:32:54.753 に答える
5

にかなりスポットを当てた回答がすでに1つあることに気づきましたが、それに対する私の見解も投稿したかったです(ここにも投稿しました)。

私の場合は、null コントロールをより安全に処理でき、必要に応じて結果を返すことができるという点で少し異なります。これらは両方とも、null の可能性がある親フォームに MessageBox を表示して Invoke を試み、その MessageBox を表示する DialogResult を返すときに役立ちます。


using System;
using System.Windows.Forms;

/// <summary>
/// Extension methods acting on Control objects.
/// </summary>
internal static class ControlExtensionMethods
{
    /// <summary>
    /// Invokes the given action on the given control's UI thread, if invocation is needed.
    /// </summary>
    /// <param name="control">Control on whose UI thread to possibly invoke.</param>
    /// <param name="action">Action to be invoked on the given control.</param>
    public static void MaybeInvoke(this Control control, Action action)
    {
        if (control != null && control.InvokeRequired)
        {
            control.Invoke(action);
        }
        else
        {
            action();
        }
    }

    /// <summary>
    /// Maybe Invoke a Func that returns a value.
    /// </summary>
    /// <typeparam name="T">Return type of func.</typeparam>
    /// <param name="control">Control on which to maybe invoke.</param>
    /// <param name="func">Function returning a value, to invoke.</param>
    /// <returns>The result of the call to func.</returns>
    public static T MaybeInvoke<T>(this Control control, Func<T> func)
    {
        if (control != null && control.InvokeRequired)
        {
            return (T)(control.Invoke(func));
        }
        else
        {
            return func();
        }
    }
}

使用法:

myForm.MaybeInvoke(() => this.Text = "Hello world");

// Sometimes the control might be null, but that's okay.
var dialogResult = this.Parent.MaybeInvoke(() => MessageBox.Show(this, "Yes or no?", "Choice", MessageBoxButtons.YesNo));
于 2010-10-06T16:12:41.547 に答える
2

Control.InvokeUIを更新するための最良の選択であると私は確信していません。私はあなたの場合、どのような状況で呼ばれたのかわからないので、はっきりとは言えませんUpdateSummary。ただし、進行状況情報(コードスニペットから得られる印象)を表示するメカニズムとして定期的に呼び出す場合は、通常、より適切なオプションがあります。そのオプションは、ワーカースレッドにステータスをプッシュさせるのではなく、UIスレッドにステータスをポーリングさせることです。

この場合、ポーリングアプローチを検討する必要がある理由は、次のとおりです。

  • それは、UIとワーカースレッドの間の緊密な結合を壊しControl.Invokeます。
  • それはとにかくそれが属するべきUIスレッドのUIスレッドを更新する責任を負います。
  • UIスレッドは、更新をいつ、どのくらいの頻度で実行するかを指示します。
  • ワーカースレッドによって開始されたマーシャリング手法の場合のように、UIメッセージポンプがオーバーランするリスクはありません。
  • ワーカースレッドは、更新が実行されたことの確認を待ってから次の手順に進む必要はありません(つまり、UIスレッドとワーカースレッドの両方でスループットが向上します)。

したがって、ワーカースレッドからのプッシュを開始するのではなく、System.Windows.Forms.Timer表示されるテキストを定期的にチェックするを作成することを検討してください。Control繰り返しになりますが、あなたの正確な要件を知らずに、私はこれをあなたが進むべき方向を明確に言うつもりはありませんが、ほとんどの場合、それオプションよりも優れていControl.Invokeます。

明らかに、このアプローチはInvokedRequiredチェックの必要性を完全に排除します。気にしないでください、それがUI/ワーカースレッドの相互作用の他のすべての側面を単純化するという事実。

于 2010-10-06T15:54:02.607 に答える
2

表示専用コントロールに対する私の推奨するアプローチは、一貫性のない状態を経ることなく更新できるクラスにすべてのコントロール状態をカプセル化することです (これを行う簡単な方法は、更新する必要があるすべてのものを不変のクラスを作成し、更新が必要になるたびにクラスの新しいインスタンスを作成します)。次に、UpdateNeeded フラグを Interlocked.Exchange するメソッドを用意し、保留中の更新がないが IsHandleCreated が true の場合は、更新手順を BeginInvoke します。更新手順では、更新を行う前に、最初に updateNeeded フラグをクリアする必要があります (その時点で誰かがコントロールを更新しようとすると、別の要求が BeginInvoked になります)。

ちなみに、コントロールがまだスレッドに結合されていない場合 (可視ウィンドウに追加されるか、コントロールが存在するウィンドウが可視になることによって)、それを直接更新することは合法ですが、BeginInvoke または Invoke を使用することは合法ではありません。

于 2010-10-06T16:10:19.803 に答える
0

可能であれば、UI をレスポンシブにするために BackgroudWorker を使用し、ReportProgress を使用して UI を更新する方が簡単です。ReportProgress は UI と同じスレッドで実行されるため、InvokeRequired は必要ありません。

于 2010-10-06T18:23:21.933 に答える