Alexander Bird
Alexander Bird

Reputation: 40639

Are C# events synchronous?

There are two parts to this question:

  1. Does raising an event block the thread, or does it start execution of EventHandlers asynchronously and the thread goes continues on at the same time?

  2. Are the individual EventHandlers (subscribed to the event) run synchronously one after another, or are they run asynchronously with no guarantee that others aren't running at the same time?

Upvotes: 127

Views: 37455

Answers (7)

Daniel Hilgarth
Daniel Hilgarth

Reputation: 174309

This is a general answer and reflects the default behavior:

  1. Yes, it blocks the thread, if the methods subscribing to the event are not asynchronous.
  2. 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 the EventsHelper class. (I am not affiliated in any way)

Upvotes: 83

KFL
KFL

Reputation: 17850

Yes, they are synchronous.

To answer your questions:

  1. Raising an event does block the thread if the event handlers are all implemented synchronously.
  2. 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:

enter image description here

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 in Do()

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.

Upvotes: 69

Vladimir
Vladimir

Reputation: 3689

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;

Upvotes: 15

Joel Etherton
Joel Etherton

Reputation: 37533

Events are synchronous. This is why the event lifecycle works the way it does. Inits happen before loads, loads happen before renders etc.

If no handler is specified for an event, the cycle just blazes through. If more than one handler is specified, they will be called in order and one can't continue until the other is completely finished.

Even asynchronous calls are synchronous to a degree. It would be impossible to call the end before the begin is completed.

Upvotes: 4

Doc Brown
Doc Brown

Reputation: 20044

Events in C# run synchronously (in both cases), as long as you don't start a second thread manually.

Upvotes: 4

Jamiec
Jamiec

Reputation: 136104

In general, events are synchronous. However there are some exceptions, such as System.Timers.Timer.Elapsed event being raised on a ThreadPool thread if SyncronisingObject is null.

Docs: http://msdn.microsoft.com/en-us/library/system.timers.timer.elapsed.aspx

Upvotes: 9

Andrey Agibalov
Andrey Agibalov

Reputation: 7694

Events are just arrays of delegates. As long as delegate call is synchronous, events are also synchronous.

Upvotes: 13

Related Questions