Are C# events synchronous?
This is a general answer and reflects the default behavior:
- Yes, it blocks the thread, if the methods subscribing to the event are not asynchronous.
- They are executed one after the other. This has another twist: If one event handler throws an exception, the event handlers not yet executed will not be executed.
Having said that, every class that provides events can choose to implement its event asynchronously. IDesign provides a class called EventsHelper
that simplifies this.
[Note] this link requires you to provide an e-mail address to download EventsHelper class. (I am not affiliated in any way)
Yes, they are synchronous.
To answer your questions:
- Raising an event does block the thread if the event handlers are all implemented synchronously.
- The event handlers are executed sequentially, one after another, in the order they are subscribed to the event.
I too was curious about the internal mechanism of event
and its related operations. So I wrote a simple program and used ildasm
to poke around its implementation.
The short answer is
- there's no asynchronous operation involved in subscribing or invoking the events.
- event is implemented with a backing delegate field of the same delegate type
- subscribing is done with
Delegate.Combine()
- unsubscribing is done with
Delegate.Remove()
- Invoking is done by simply invoking the final combined delegate
Here's what I did. The program I used:
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();
}
}
Here's Foo's implementation:
Note that there is a field OnCall
and an event OnCall
. The field OnCall
is obviously the backing property. And it's merely a Func<int, string>
, nothing fancy here.
Now the interesting parts are:
add_OnCall(Func<int, string>)
remove_OnCall(Func<int, string>)
- and how
OnCall
is invoked inDo()
How is Subscribing and Unsubscribing Implemented?
Here's the abbreviated add_OnCall
implementation in CIL. The interesting part is it uses Delegate.Combine
to concatenate two delegates.
.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
Likewise, Delegate.Remove
is used in remove_OnCall
.
How is an event invoked?
To invoke OnCall
in Do()
, it simply calls the final concatenated delegate after loading the arg:
IL_0026: callvirt instance !1 class [mscorlib]System.Func`2<int32,string>::Invoke(!0)
How exactly does a subscriber subscribe to an event?
And finally, in Main
, not suprisingly, subscribing to the OnCall
event is done by calling add_OnCall
method on the Foo
instance.
The delegates subscribed to the event are invoked synchronously in the order they were added. If one of the delegates throws an exception, the ones following will not be called.
Since events are defined with multicast delegates, you can write your own firing mechanism using
Delegate.GetInvocationList();
and invoking the delegates asynchronously;