199

イベント ドリブンの 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型のショートカットのように、この余分な作業をすべて行う必要なく、このチェックを行う関数をオブジェクトにアタッチする方法があれば素晴らしいと思います。

以前の回答では、毎回 Invoke() を呼び出すだけでは非現実的であると説明されていましたが、それでもInvoke() 構文は非効率的で扱いにくいものです。

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

4

9 に答える 9

151

Leeのアプローチはさらに単純化できます

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# は自動的にクロージャーを作成します。

値を返す必要がある場合は、次の実装を使用できます。

private static T InvokeIfRequiredReturn<T>(this Control control, Func<T> function)
{
    if (control.InvokeRequired) {
        return (T)control.Invoke(function);
    } else {
        return function();
    }
}

更新

他のいくつかのポスターによると、次のControlように一般化できますISynchronizeInvoke

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

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


更新 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とnawfalコメントを参照してください。

于 2012-08-29T13:44:20.443 に答える
138

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

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
于 2010-03-02T23:37:09.557 に答える
40

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

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

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

お役に立てれば。

于 2010-03-03T00:05:28.887 に答える
10

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>
于 2011-02-15T13:42:56.097 に答える
8

これは、Lee、Oliver、および Stephan の回答の改良版/結合版です。

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;
});
于 2015-04-07T17:37:26.447 に答える
6

毎回新しいインスタンスを作成するのではなく、メソッド 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);
}
于 2013-06-21T11:20:18.383 に答える
3

私は少し違うやり方をしたいのですが、アクションで必要に応じて「自分自身」と呼ぶのが好きです。

    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 に設定するフィールドです...

于 2016-12-21T22:07:11.677 に答える
-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 プロパティに加えて、コントロールのハンドルが既に作成されている場合は、呼び出しがスレッド セーフなコントロールの 4 つのメソッド、Invoke、BeginInvoke、EndInvoke、および CreateGraphics があります。

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

于 2014-10-29T13:10:30.227 に答える