呼び出す(デリゲート)


93

誰かがこのリンクに書かれたこの声明を説明できますか

Invoke(Delegate):

コントロールの基になるウィンドウハンドルを所有するスレッドで、指定されたデリゲートを実行します

これが何を意味するのか誰でも説明できますか(特に太字の場合)、はっきりと理解できません


4
この質問への答えは、Control.InvokeRequiredプロパティに関連付けられています-msdn.microsoft.com/en-us/library/…を
ダッシュ

回答:


130

この質問への答えは、C#コントロールのしくみにあります

Windowsフォームのコントロールは特定のスレッドにバインドされており、スレッドセーフではありません。したがって、別のスレッドからコントロールのメソッドを呼び出す場合は、コントロールの呼び出しメソッドの1つを使用して、適切なスレッドへの呼び出しをマーシャリングする必要があります。このプロパティを使用して、invokeメソッドを呼び出す必要があるかどうかを判断できます。これは、どのスレッドがコントロールを所有しているかわからない場合に役立ちます。

Control.InvokeRequiredから

Invokeが効果的に行うのは、呼び出しているコードが、コントロールが「存続」しているスレッドで発生し、クロススレッドの例外を効果的に防ぐことです。

歴史的な観点から、.Net 1.1では、これは実際に許可されていました。これは、バックグラウンドスレッドから「GUI」スレッドでコードを実行できることを意味し、これはほとんど機能します。GUIスレッドが何か他のことをしているときに効果的に割り込んでいたために、アプリが終了してしまう場合があります。これはクロススレッドの例外です。GUIが何かをペイントしているときにTextBoxを更新しようとしていると想像してください。

  • どのアクションが優先されますか?
  • 両方が同時に発生することも可能ですか?
  • GUIが実行する必要がある他のすべてのコマンドはどうなりますか?

実質的には、キューに割り込んでいるため、予期しない結果が数多く生じる可能性があります。Invokeは事実上、実行したいことをそのキューに入れる「丁寧な」方法であり、このルールは、スローされたInvalidOperationExceptionを介して.Net 2.0以降から適用されました。

実際に舞台裏で何が行われているのか、および「GUIスレッド」が何を意味しているのかを理解するには、メッセージポンプまたはメッセージループとは何かを理解することが役立ちます。

これは、「メッセージポンプとは、コントロールを操作するときに結び付けている実際のメカニズムを理解するために読むことをお勧めします。

あなたが役立つかもしれない他の読書は以下を含みます:

Begin Invokeの概要

Windows GUIプログラミングの基本的なルールの1つは、コントロールを作成したスレッドだけがそのコンテンツにアクセスしたり、コンテンツを変更したりできるということです(ドキュメント化されたいくつかの例外を除く)。他のスレッドから実行してみてください。デッドロックから、例外から半分更新されたUIまで、予測できない動作が発生します。別のスレッドからコントロールを更新する正しい方法は、適切なメッセージをアプリケーションメッセージキューにポストすることです。メッセージポンプがそのメッセージの実行に取り掛かると、コントロールはそれを作成したのと同じスレッドで更新されます(メッセージポンプはメインスレッドで実行されることに注意してください)。

また、代表的なサンプルを使用してコードを多用した概要は、次のとおりです。

無効なクロススレッド操作

// the canonical form (C# consumer)

public delegate void ControlStringConsumer(Control control, string text);  // defines a delegate type

public void SetText(Control control, string text) {
    if (control.InvokeRequired) {
        control.Invoke(new ControlStringConsumer(SetText), new object[]{control, text});  // invoking itself
    } else {
        control.Text=text;      // the "functional part", executing only on the main thread
    }
}

InvokeRequiredの評価が得られたら、これらの呼び出しをまとめるために拡張メソッドを使用することを検討できます。これは、Stack Overflowの質問Cleaning Up Code Littered With Littered with Invoke Requiredで適切にカバーされています。

さらにもあり、歴史的に何が起こったかの書き込みまで関心のものであってもよいです。


67

Windowsフォームのコントロールまたはウィンドウオブジェクトは、ハンドル(HWNDと呼ばれることもあります)で識別されるWin32ウィンドウの単なるラッパーです。コントロールで行うほとんどのことは、最終的にこのハンドルを使用するWin32 API呼び出しになります。ハンドルは、それを作成したスレッド(通常はメインスレッド)が所有しており、別のスレッドによって操作されるべきではありません。何らかの理由で別のスレッドからのコントロールで何かを行う必要がある場合は、を使用Invokeして、メインスレッドに代わりにそれを実行するように要求できます。

たとえば、ワーカースレッドからラベルのテキストを変更する場合は、次のようなことができます。

theLabel.Invoke(new Action(() => theLabel.Text = "hello world from worker thread!"));

誰かがこのようなことをする理由を説明できますか?確かに現在のスレッドにあるものはthis.Invoke(() => this.Enabled = true);thisですか?
カイルデラニー2017

1
@KyleDelaney、オブジェクトはスレッド内にあるわけではなく、現在のスレッドは必ずしもオブジェクトを作成したスレッドであるとは限りません。
Thomas Levesque 2017

24

コントロールを変更する場合は、コントロールが作成されたスレッドで行う必要があります。このInvokeメソッドを使用すると、関連するスレッド(コントロールの基になるウィンドウハンドルを所有するスレッド)でメソッドを実行できます。

以下のサンプルでは、​​SetText1が別のスレッドのtextBox1.Textを変更しようとしているため、thread1は例外をスローします。ただし、thread2では、SetText2のActionは、TextBoxが作成されたスレッドで実行されます。

private void btn_Click(object sender, EvenetArgs e)
{
    var thread1 = new Thread(SetText1);
    var thread2 = new Thread(SetText2);
    thread1.Start();
    thread2.Start();
}

private void SetText1() 
{
    textBox1.Text = "Test";
}

private void SetText2() 
{
    textBox1.Invoke(new Action(() => textBox1.Text = "Test"));
}

私はこのアプローチが本当に好きです。デリゲートの性質を隠しますが、とにかく素晴らしいショートカットです。
shytikov

6
Invoke((MethodInvoker)delegate{ textBox1.Text = "Test"; });

System.Actionの使用が他の応答で提案するのはフレームワーク3.5+でのみ機能し、古いバージョンでは完全に機能します
Suicide Platypus

2

実際には、デリゲートがメインスレッドで呼び出されることが保証されていることを意味します。Windowsコントロールの場合、メインスレッドでプロパティを更新しないと、変更が表示されないか、コントロールで例外が発生するため、これは重要です。

パターンは次のとおりです。

void OnEvent(object sender, EventArgs e)
{
   if (this.InvokeRequired)
   {
       this.Invoke(() => this.OnEvent(sender, e);
       return;
   }

   // do stuff (now you know you are on the main thread)
}

2

this.Invoke(delegate)this.Invoke()メインスレッド/作成されたスレッドの引数をデリゲートに呼び出していることを確認してください。

Thumbルールは、メインスレッド以外ではフォームコントロールにアクセスしないと言えます。

次の行は、Invoke()を使用する意味があります。

    private void SetText(string text)
    {
        // InvokeRequired required compares the thread ID of the
        // calling thread to the thread ID of the creating thread.
        // If these threads are different, it returns true.
        if (this.textBox1.InvokeRequired)
        {   
            SetTextCallback d = new SetTextCallback(SetText);
            this.Invoke(d, new object[] { text });
        }
        else
        {
            this.textBox1.Text = text;
        }
    }

メインスレッドで実行されるスレッドプールスレッド(つまり、ワーカースレッド)を作成する場合もあります。それは新しいスレッドを作成しませんcozメインスレッドはさらなる命令を処理するために利用可能です。したがって、まず、現在実行中のスレッドがメインスレッドであるかどうかを調査しthis.InvokeRequiredます。

それ以外の場合は、UIコントロールを直接更新します(メインスレッドでコードを実行していることが保証されます)。


1

つまり、バックグラウンドワーカーまたはスレッドプールスレッドからそのメソッドを呼び出した場合でも、デリゲートはUIスレッドで実行されます。UI要素にはスレッドアフィニティがあります。これらの要素は、1つのスレッド(UIスレッド)と直接対話することだけが好きです。UIスレッドは、コントロールインスタンスを作成たスレッドとして定義されているため、ウィンドウハンドルに関連付けられています。しかし、それはすべて実装の詳細です。

重要な点は、UIにアクセスできるように(ラベルの値を変更するなど)、ワーカースレッドからこのメソッドを呼び出すことです。UIスレッド以外のスレッドからは実行できないためです。


0

デリゲートは本質的にインラインActionのまたはFunc<T>です。デリゲートは、実行中のメソッドのスコープ外で宣言するか、lambda式(=>)を使用して宣言できます。メソッド内でデリゲートを実行するので、太字のビットである現在のウィンドウ/アプリケーションに対して実行されているスレッドでデリゲートを実行します。

ラムダの例

int AddFiveToNumber(int number)
{
  var d = (int i => i + 5);
  d.Invoke(number);
}

0

つまり、渡したデリゲートは、Controlオブジェクトを作成したスレッド(UIスレッド)で実行されます。

アプリケーションがマルチスレッドであり、UIスレッド以外のスレッドからUI操作を実行する場合は、このメソッドを呼び出す必要があります。別のスレッドからControlのメソッドを呼び出そうとすると、 System.InvalidOperationException。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.