web-dev-qa-db-ja.com

InvokeRequiredで散らかったコードのクリーンアップ

非UIスレッドからUIコントロールを操作するときは、問題を回避するために、UIスレッドへの呼び出しをマーシャリングする必要があることを知っています。一般的なコンセンサスは、テスト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テストを維持するより良い方法はありますか?

46
Erik Forbes

まあこれはどうですか:

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 });
}
65
John Gietzen

UIスレッドからInvokeを呼び出すのは少し効率的ではありません。

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

8
SLaks

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-デバッグ:[デバッグ]ストップウォッチ[UIスレッドに直接]:0ms
  • TimedAction :: Go()+ 0-デバッグ:[デバッグ]ストップウォッチ[UIスレッドで呼び出す]:299ms
  • TimedAction :: Go()+ 0-デバッグ:[デバッグ]ストップウォッチ[UIスレッドで個別に呼び出す]:649ms

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

7
Michael

私はすでに かなり多くの点が当てはまる1つの回答 があることを理解していますが、私の見解も投稿したいと思います(私も here を投稿しました)。

Mineは、nullコントロールを少し安全に処理でき、必要に応じて結果を返すことができるという点で少し異なります。 nullの可能性がある親フォームにMessageBoxを表示して、その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));
5
Mark Rushakoff

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

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

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

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

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

3
Brian Gideon

表示専用コントロールの推奨アプローチは、一貫性のない状態を経ることなく更新できるクラスにすべてのコントロール状態をカプセル化することです(これを行う簡単な方法は、更新する必要があるすべてのものをまとめて不変クラスであり、更新が必要な場合は常にクラスの新しいインスタンスを作成します。次に、Interlocked.ExchangeにupdateNeededフラグを設定するメソッドを用意します。保留中の更新がないが、IsHandleCreatedがtrueの場合は、更新手順をBeginInvokeします。更新手順では、更新を行う前に、最初にupdateNeededフラグをクリアする必要があります(その時点で誰かがコントロールを更新しようとすると、別の要求がBeginInvokedになります)。更新を準備しているときと同じようにコントロールが破棄された場合は、例外(IllegalOperationだと思う)をキャッチして飲み込む準備をしておく必要があることに注意してください。

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

2
supercat

私はまだコメントできません。うまくいけば、誰かがこれを見て、受け入れられた回答に追加することを望みます。

control.Invoke(new Action(() => action(control)));は読むべきです
control.Invoke(new Action(() => action(control)), null);

書かれているように、ISynchronizeInvoke.Invoke()Control.Invoke()のように引数が1つしかないオーバーロードがないため、受け入れられた回答はコンパイルされません。

もう1つは、次のように使用法がより明確になる場合があることです。
summary.InvokeIfRequired(c => { summary.Text = text; });書かれたものではなくsummary.InvokeIfRequired(c => { textBox.Text = text });

1
Joseph A

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

0
sh_kamalh