web-dev-qa-db-ja.com

C#イベントは同期的ですか?

この質問には2つの部分があります。

  1. raisingイベントはスレッドをブロックしますか、それともEventHandlerの実行を非同期に開始し、スレッドが同時に継続しますか?

  2. 個別のEventHandlers(イベントにサブスクライブ)は同期して次々に実行されますか?

95
Alexander Bird

質問に答えるには:

  1. イベントハンドラーがすべて同期的に実装されている場合、イベントを発生させるとスレッドがブロックされます。
  2. イベントハンドラーは、イベントにサブスクライブされた順番で、順番に実行されます。

私もeventの内部メカニズムとそれに関連する操作に興味がありました。そこで、簡単なプログラムを作成し、ildasmを使用してその実装を調べました。

短い答えは

  • イベントのサブスクライブまたは呼び出しに関連する非同期操作はありません。
  • イベントは、同じデリゲートタイプのバッキングデリゲートフィールドで実装されます
  • 購読は Delegate.Combine() で行われます
  • 購読解除は Delegate.Remove() で行われます
  • 呼び出しは、最終的な結合デリゲートを呼び出すだけで実行されます。

これが私がしたことです。私が使用したプログラム:

_public class Foo
{
    // cool, it can return a value! which value it returns if there're multiple 
    // subscribers? answer (by trying): the last subscriber.
    public event Func<int, string> OnCall;
    private int val = 1;

    public void Do()
    {
        if (OnCall != null) 
        {
            var res = OnCall(val++);
            Console.WriteLine($"publisher got back a {res}");
        }
    }
}

public class Program
{
    static void Main(string[] args)
    {
        var foo = new Foo();

        foo.OnCall += i =>
        {
            Console.WriteLine($"sub2: I've got a {i}");
            return "sub2";
        };

        foo.OnCall += i =>
        {
            Console.WriteLine($"sub1: I've got a {i}");
            return "sub1";
        };

        foo.Do();
        foo.Do();
    }
}
_

Fooの実装は次のとおりです。

enter image description here

fieldOnCalleventOnCallがあることに注意してください。フィールドOnCallは明らかにバッキングプロパティです。そして、それは単に_Func<int, string>_であり、ここでは特に素晴らしいものではありません。

興味深い部分は次のとおりです。

  • add_OnCall(Func<int, string>)
  • remove_OnCall(Func<int, string>)
  • Do()OnCallを呼び出す方法

サブスクライブとサブスクライブ解除はどのように実装されますか?

以下は、CILでの_add_OnCall_の短縮実装です。興味深い部分は、2つのデリゲートを連結するために _Delegate.Combine_ を使用していることです。

_.method public hidebysig specialname instance void 
        add_OnCall(class [mscorlib]System.Func`2<int32,string> 'value') cil managed
{
  // ...
  .locals init (class [mscorlib]System.Func`2<int32,string> V_0,
           class [mscorlib]System.Func`2<int32,string> V_1,
           class [mscorlib]System.Func`2<int32,string> V_2)
  IL_0000:  ldarg.0
  IL_0001:  ldfld      class [mscorlib]System.Func`2<int32,string> ConsoleApp1.Foo::OnCall
  // ...
  IL_000b:  call       class [mscorlib]System.Delegate [mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate,
                                                                                          class [mscorlib]System.Delegate)
  // ...
} // end of method Foo::add_OnCall
_

同様に、_Delegate.Remove_は_remove_OnCall_で使用されます。

イベントはどのように呼び出されますか?

Do()OnCallを呼び出すには、引数を読み込んだ後、最終的な連結デリゲートを呼び出します。

_IL_0026:  callvirt   instance !1 class [mscorlib]System.Func`2<int32,string>::Invoke(!0)
_

サブスクライバーはどのくらい正確にイベントをサブスクライブしますか?

そして最後に、Mainでは、驚くことではなく、OnCallイベントをサブスクライブするには、Fooインスタンスで_add_OnCall_メソッドを呼び出します。

26
KFL

これは一般的な答えであり、デフォルトの動作を反映しています。

  1. はい、イベントにサブスクライブしているメソッドが非同期でない場合、スレッドをブロックします。
  2. それらは次々に実行されます。これには別の工夫があります。1つのイベントハンドラーが例外をスローした場合、まだ実行されていないイベントハンドラーは実行されません。

そうは言っても、イベントを提供するすべてのクラスは、イベントを非同期に実装することを選択できます。 IDesign は、これを簡素化する EventsHelper というクラスを提供します。

[注]このリンクでは、電子メールを入力する必要がありますEventsHelperクラスをダウンロードするアドレス。 (私はいかなる形でも提携していません)

70
Daniel Hilgarth

イベントにサブスクライブされたデリゲートは、追加された順に同期的に呼び出されます。デリゲートの1つが例外をスローした場合、will notに続くものが呼び出されます。

イベントはマルチキャストデリゲートで定義されているため、次を使用して独自の起動メカニズムを作成できます。

Delegate.GetInvocationList();

デリゲートを非同期に呼び出します。

14
Vladimir

イベントはデリゲートの配列です。デリゲート呼び出しが同期的である限り、イベントも同期的です。

12
Andrey Agibalov

一般に、イベントは同期的です。ただし、ThreadPoolがnullの場合、SyncronisingObjectスレッドでSystem.Timers.Timer.Elapsedイベントが発生するなど、いくつかの例外があります。

ドキュメント: http://msdn.Microsoft.com/en-us/library/system.timers.timer.elapsed.aspx

7
Jamiec

2番目のスレッドを手動で開始しない限り、C#のイベントは同期的に実行されます(両方の場合)。

3
Doc Brown

イベントは同期的です。これが、イベントライフサイクルがそのように機能する理由です。初期化はロードの前に発生し、ロードはレンダリングの前に発生します。

イベントにハンドラが指定されていない場合、サイクルはただ燃え上がります。複数のハンドラーが指定されている場合、それらは順番に呼び出され、もう一方のハンドラーが完全に終了するまで続行できません。

非同期呼び出しでもある程度同期します。開始が完了する前に終了を呼び出すことは不可能です。

3
Joel Etherton