web-dev-qa-db-ja.com

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

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

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

だから、誰かがショートカットを見つけましたか?

175
Tom Corelis

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

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#は自動的に closure を作成します。


UPDATE

他のいくつかのポスターによると、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は、Controlとは異なり、ISynchronizeInvokeインターフェイスにはInvokeのパラメーターリストとしてactionメソッドのオブジェクト配列が必要であると指摘しました。


UPDATE 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のコメントを参照してください。

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

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
133
Lee

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

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

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

お役に立てれば。

33
Matt Davis

ThreadSafeInvoke.snippetファイルを作成すると、更新ステートメントを選択し、右クリックして[…で囲む]または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>
9
Aaron Gage

以下は、リー、オリバー、ステファンの回答の改良版/組み合わせ版です。

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;
});
6
gxtaillon

毎回新しいインスタンスを作成するのではなく、メソッド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);
}
4
stephan Schmuck

使用法:

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);
        }
    }
}
3
Konstantin S.

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

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

2