InvokeRequiredコードパターンの自動化


179

イベント駆動型GUIコードで次のコードパターンを記述する必要がある頻度を痛感しました。

private void DoGUISwitch() {
    // cruisin for a bruisin' through exception city
    object1.Visible = true;
    object2.Visible = false;
}

になる:

private void DoGUISwitch() {
    if (object1.InvokeRequired) {
        object1.Invoke(new MethodInvoker(() => { DoGUISwitch(); }));
    } else {
        object1.Visible = true;
        object2.Visible = false;
    }
}

これは、覚えたり入力したりするための、C#での厄介なパターンです。誰かがこれをある程度自動化する何らかのショートカットや構造を思いついたことがありますか?object1.InvokeIfNecessary.visible = trueタイプショートカットのように、この追加のすべての作業を行わずに、このチェックを行う関数をオブジェクトにアタッチする方法があったら、すばらしいでしょう。

前の答えはちょうどたびに呼び出しを()を呼び出すの非現実を議論してきた、とさえ[起動()構文が非効率的との両方で、まだ対処しにくいです。

それで、誰かがショートカットを見つけましたか?


2
私は同じことを思ったが、WPFのDispatcher.CheckAccess()に関して。
テイラーリース

私はあなたのobject1.InvokeIfNecessary.Visible = trueラインに触発されたかなりクレイジーな提案を考えました。私の更新された答えをチェックして、あなたの考えを知らせてください。
Dan Tao

1
スニペットを追加して、マットデイビスが提案した方法を実装します。私の回答を参照してください(遅いですが、後の読者のために方法を示しています;
Aaron Gage

3
Microsoftが.NETでそれを簡略化するために何もしなかった理由がわかりません。スレッドからフォームに変更を加えるたびにデリゲートを作成するのは本当に面倒です。
カミル

@カミル私はこれ以上同意できませんでした!その遍在性を考えると、これはそのような見落としです。フレームワーク内で、必要に応じてスレッドを処理します。明らかなようです。
SteveCinq

回答:


138

リーのアプローチはさらに簡略化できます

public static void InvokeIfRequired(this Control control, MethodInvoker action)
{
    // See Update 2 for edits Mike de Klerk suggests to insert here.

    if (control.InvokeRequired) {
        control.Invoke(action);
    } else {
        action();
    }
}

そして、このように呼び出すことができます

richEditControl1.InvokeIfRequired(() =>
{
    // Do anything you want with the control here
    richEditControl1.RtfText = value;
    RtfHelpers.AddMissingStyles(richEditControl1);
});

コントロールをパラメーターとしてデリゲートに渡す必要はありません。C#は自動的にクロージャを作成します。


更新

他のいくつかのポスターによるとControlISynchronizeInvoke次のように一般化することができます:

public static void InvokeIfRequired(this ISynchronizeInvoke obj,
                                         MethodInvoker action)
{
    if (obj.InvokeRequired) {
        var args = new object[0];
        obj.Invoke(action, args);
    } else {
        action();
    }
}

DonBoitnottはControlISynchronizeInvokeインターフェイスとは異なり、Invokeメソッドのオブジェクト配列がのパラメータリストとして必要であることを指摘しましたaction


アップデート2

Mike de Klerkによって提案された編集(挿入ポイントについては、最初のコードスニペットのコメントを参照):

// When the form, thus the control, isn't visible yet, InvokeRequired  returns false,
// resulting still in a cross-thread exception.
while (!control.Visible)
{
    System.Threading.Thread.Sleep(50);
}

この提案に関する懸念については、以下のToolmakerSteveのコメントを参照してください。


2
ISynchronizeInvoke代わりに持つ方がいいのではないでしょうControlか?(ジョンスキートのに賞賛stackoverflow.com/questions/711408/...
ODYS

@odyodyodys:良い点。知りませんでしたISynchronizeInvoke。ただし、(Reflectorによると)そこから派生するタイプはだけなControlので、利点は限られています。
Olivier Jacot-Descombes 2013年

3
@ mike-de-clerk、私はあなたが追加する提案を心配していますwhile (!control.Visible) ..sleep..。それは潜在的に無制限の遅延(場合によっては無限ループでさえあるかもしれない)であり、そのような遅延(またはデッドロックさえ)を期待していない呼び出し元がいる可能性があるコードでは、コードの臭いが悪いと私には考えられます。IMHO、の使用はSleep各呼び出し元の責任である必要があります。または、その結果について明確にマークされている個別のラッパーで行う必要があります。IMHO、通常は「ハードに失敗する」(例外、テスト中に捕捉する)か、コントロールが準備ができていない場合は「何もしない」方が良いでしょう。コメント?
ToolmakerSteve

1
@ OlivierJacot-Descombes、thread.invokerequiredがどのように機能するかを説明していただければ、すばらしいと思いますか?
Sudhir.net

1
InvokeRequired呼び出し側のスレッドが、コントロールを作成したスレッドと異なるかどうかを示します。Invoke呼び出し側のスレッドから、アクションが実行されるコントロールのスレッドにアクションを渡します。これにより、たとえば、クリックイベントハンドラが中断されることがなくなります。
Olivier Jacot-Descombes

133

あなたは拡張メソッドを書くことができます:

public static void InvokeIfRequired(this Control c, Action<Control> action)
{
    if(c.InvokeRequired)
    {
        c.Invoke(new Action(() => action(c)));
    }
    else
    {
        action(c);
    }
}

次のように使用します。

object1.InvokeIfRequired(c => { c.Visible = true; });

編集:Simpzonがコメントで指摘しているように、署名を次のように変更することもできます:

public static void InvokeIfRequired<T>(this T c, Action<T> action) 
    where T : Control

多分私は馬鹿げているかもしれませんが、このコードはコンパイルされません。だから私はそれを自分で構築したように修正しました(VS2008)。
オリバー

5
完全を期すために:WPFには別のディスパッチメカニズムがありますが、これはかなり類似しています。そこでこの拡張メソッドを使用できます。public static void InvokeIfRequired <T>(this T aTarget、Action <T> aActionToExecute)where T:DispatcherObject {if(aTarget.CheckAccess()){aActionToExecute(aTarget); } else {aTarget.Dispatcher.Invoke(aActionToExecute); }}
Simon D.

1
Leeのソリューションを少し簡略化する回答を追加しました。
Olivier Jacot-Descombes 2012

こんにちは、私が似たようなものを使用している場合、この一般的な実装から大きな問題が発生する可能性があります。コントロールがDisposed / Disposedの場合、ObjectDisposedExceptionが発生します。
Offler

1
@Offler-それらが別のスレッドで破棄されている場合、同期の問題がありますが、この方法では問題になりません。
Lee

33

これが、すべてのコードで使用しているフォームです。

private void DoGUISwitch()
{ 
    Invoke( ( MethodInvoker ) delegate {
        object1.Visible = true;
        object2.Visible = false;
    });
} 

これは、こちらのブログエントリに基づいています。私はこのアプローチで失敗することはなかったので、InvokeRequiredプロパティのチェックでコードを複雑にする理由はありません。

お役に立てれば。


1 -私はあなたがやった同じブログエントリにつまずいたし、これは任意の提案のクリーンなアプローチだと思う
トム・ブッシェル

3
このアプローチを使用すると、パフォーマンスがわずかに低下します。複数回呼び出されると、パフォーマンスが低下する可能性があります。stackoverflow.com/a/747218/724944
サーフェン

4
あなたは使用する必要がありInvokeRequired、コードが実行されることができれば、コントロールが示された前か、致命的な例外があります。
56ka 2014年

9

ThreadSafeInvoke.snippetファイルを作成すると、更新ステートメントを選択し、右クリックして[Surround With ...]またはCtrl-K + Sを選択できます。

<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippet Format="1.0.0" xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
  <Header>
    <Title>ThreadsafeInvoke</Title>
    <Shortcut></Shortcut>
    <Description>Wraps code in an anonymous method passed to Invoke for Thread safety.</Description>
    <SnippetTypes>
      <SnippetType>SurroundsWith</SnippetType>
    </SnippetTypes>
  </Header>
  <Snippet>
    <Code Language="CSharp">
      <![CDATA[
      Invoke( (MethodInvoker) delegate
      {
          $selected$
      });      
      ]]>
    </Code>
  </Snippet>
</CodeSnippet>

6

これは、リーズ、オリバーズ、ステファンの回答の改良/結合バージョンです。

public delegate void InvokeIfRequiredDelegate<T>(T obj)
    where T : ISynchronizeInvoke;

public static void InvokeIfRequired<T>(this T obj, InvokeIfRequiredDelegate<T> action)
    where T : ISynchronizeInvoke
{
    if (obj.InvokeRequired)
    {
        obj.Invoke(action, new object[] { obj });
    }
    else
    {
        action(obj);
    }
} 

テンプレートは、専用のデリゲートが効率を提供する一方で、はるかに読みやすい柔軟でキャストレスなコードを可能にします。

progressBar1.InvokeIfRequired(o => 
{
    o.Style = ProgressBarStyle.Marquee;
    o.MarqueeAnimationSpeed = 40;
});

4

毎回新しいインスタンスを作成するのではなく、メソッドDelegateの単一のインスタンスを使用したいのです。私の場合、SQLインスタンスから大きなデータをコピーしてキャストするBackroundworkerからの進行状況と(情報/エラー)メッセージを表示するのが常でした。約70000の進捗状況とメッセージ呼び出しの後、フォームが機能しなくなり、新しいメッセージが表示されなくなりました。これは、単一のグローバルインスタンスデリゲートを使い始めたときには発生しませんでした。

delegate void ShowMessageCallback(string message);

private void Form1_Load(object sender, EventArgs e)
{
    ShowMessageCallback showMessageDelegate = new ShowMessageCallback(ShowMessage);
}

private void ShowMessage(string message)
{
    if (this.InvokeRequired)
        this.Invoke(showMessageDelegate, message);
    else
        labelMessage.Text = message;           
}

void Message_OnMessage(object sender, Utilities.Message.MessageEventArgs e)
{
    ShowMessage(e.Message);
}

3

使用法:

control.InvokeIfRequired(c => c.Visible = false);

return control.InvokeIfRequired(c => {
    c.Visible = value

    return c.Visible;
});

コード:

using System;
using System.ComponentModel;

namespace Extensions
{
    public static class SynchronizeInvokeExtensions
    {
        public static void InvokeIfRequired<T>(this T obj, Action<T> action)
            where T : ISynchronizeInvoke
        {
            if (obj.InvokeRequired)
            {
                obj.Invoke(action, new object[] { obj });
            }
            else
            {
                action(obj);
            }
        }

        public static TOut InvokeIfRequired<TIn, TOut>(this TIn obj, Func<TIn, TOut> func) 
            where TIn : ISynchronizeInvoke
        {
            return obj.InvokeRequired
                ? (TOut)obj.Invoke(func, new object[] { obj })
                : func(obj);
        }
    }
}

2

ちょっと違うことをしたいのですが、アクションで必要に応じて「自分」を呼び出すのが好きです。

    private void AddRowToListView(ScannerRow row, bool suspend)
    {
        if (IsFormClosing)
            return;

        if (this.InvokeRequired)
        {
            var A = new Action(() => AddRowToListView(row, suspend));
            this.Invoke(A);
            return;
        }
         //as of here the Code is thread-safe

これは便利なパターンです。IsFormClosingは、フォームを閉じるときにTrueに設定するフィールドです。まだ実行中のバックグラウンドスレッドがある可能性があるためです...


-3

次のようなコードは作成しないでください。

private void DoGUISwitch() {
    if (object1.InvokeRequired) {
        object1.Invoke(new MethodInvoker(() => { DoGUISwitch(); }));
    } else {
        object1.Visible = true;
        object2.Visible = false;
    }
}

このようなコードがある場合、アプリケーションはスレッドセーフではありません。これは、別のスレッドからDoGUISwitch()をすでに呼び出しているコードがあることを意味します。別のスレッドにあるかどうかを確認するには遅すぎます。DoGUISwitchを呼び出す前に、InvokeRequireを呼び出す必要があります。別のスレッドからメソッドやプロパティにアクセスしないでください。

参照: 次を読み取ることができるControl.InvokeRequiredプロパティ

InvokeRequiredプロパティに加えて、スレッドセーフで呼び出すことができるコントロールには、Invoke、BeginInvoke、EndInvoke、およびCreateGraphicsの4つのメソッドがあります(コントロールのハンドルが既に作成されている場合)。

シングルCPUアーキテクチャでは問題はありませんが、マルチCPUアーキテクチャでは、UIスレッドの一部を、呼び出し元のコードが実行されていたプロセッサに割り当てることができます...そのプロセッサがUIスレッドの場所と異なる場合実行中のスレッドが終了すると、WindowsはUIスレッドが終了したと見なし、アプリケーションプロセスを強制終了します。つまり、アプリケーションはエラーなしで終了します。


こんにちは、あなたの答えをありがとう。私がこの質問をしてから数年が経過しました(そしてC#を使用してからほぼ同じくらい長い時間です)が、もう少し詳しく説明できるかどうか疑問に思っていましたか?あなたがリンクしたドキュメントinvoke()は、コントロールにハンドルが与えられる前にet al を呼び出す特定の危険性を参照していますが、IMHOはあなたが説明したことを説明していません。このすべてのinvoke()ナンセンスの要点は、スレッドセーフな方法でUIを更新することです。私は、ブロッキングコンテキストに命令を追加すると、どもりにつながると思いますか?(うーん...うれしい私はM $テクノロジーの使用をやめました。とても複雑です!)
Tom Corelis 2014年

また、元のコードを頻繁に使用していても(いつまで遡るか)、デュアルCPUデスクトップで説明した問題を確認できなかった
Tom Corelis

3
MSDNがOPと同じように多くの例を示しているので、この答えが正しいとは思えません。
パブリックワイヤレス
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.