Invoke of an EventHandler
Asked Answered
J

2

8

I have the following EventHandler:

private EventHandler<MyEventArgs> _myEventHandler;
public event EventHandler<MyEventArgs> MyEvent
{
  add { _myEventHandler += value; }
  remove { _myEventHandler -= value; }
}  

Could somebody explain the difference between the following snippets?
Snippet EventHandler (A):

//Snippet A:
if (_myEventHandler != null)
{
  _myEventHandler(new MyEventArgs());
}

Snippet BeginInvoke (B):

//Snippet B:
if (_myEventHandler != null)
{
  _myEventHandler.BeginInvoke(new MyEventArgs(), ar =>
  {
    var del = (EventHandler<MyEventArgs>)ar.AsyncState;
    del.EndInvoke(ar);
  }, _myEventHandler);
}

For clarification: What's the difference between invoking an EventHandler "just as it is" and using BeginInvoke?

Jauregui answered 11/8, 2011 at 9:33 Comment(0)
R
16

The BeginInvoke approach is async, meaning that it is raised on a different thread. This can be dangerous if people don't expect it, and is pretty rare for events - but it can be useful.

Also, note that strictly speaking you should snapshot the event handler value - this is especially true if (via Begin*) you are dealing with threads.

var tmp = _myEventHandler;
if(tmp != null) {
    tmp(sender, args);
}

Also - note that your event subscription itself is not thread-safe; again, this only matters if you are dealing with multi-threading, but the inbuilt field-like event is thread-safe:

public event EventHandler<MyEventArgs> MyEvent; // <===== done; nothing more

The issues avoided here are:

  • with the snapshot, we avoid the risk of the last subscriber unsubscribing between the null-check and the invoke (it does mean they might get an event they didn't expect, but it means we don't kill the raising thread)
  • with the field-like event change we avoid the risk of losing subscriptions / unsubscriptions when two threads are doing this at the same time
Riches answered 11/8, 2011 at 9:37 Comment(8)
It isn't necessarily called on a different thread is it? Calling a delegate asynchronously is still performed on the same thread but returns the moment it blocks AFAIK.Triglyph
@Jeff no; calling a delegate asynchronously means it happens on a worker thread. How else would it run asynchronously? Note that this is subtly different to Control.BeginInvoke which might continue on the same thread if you are already on the UI threadRiches
If the delegate being called is doing IO (i.e., blocks) control is returned back to the call site. When that completes, the original thread is interrupted to finish the rest of the method. As I understood it, no new threads are created, it's all interrupts from there.Triglyph
That's if you were using AsyncIO. Using EventHandlers to call delegates that are doing IO doesn't change the behavior of the EventHandler. BeginInvoke on an event handler will use the thread pool. Period.Eastwardly
@Jeff that may be true of things like FileStream.BeginRead - however, here we are simply using Delegate.BeginInvoke - which is a general purpose "run a method" API. It knows nothing about what the target method will do, so uses the thread-pool to invoke the method.Riches
@Marc: Ah, that's the part I needed to know. I didn't think that was actually done for general delegates. Thanks for straightening me out. :)Triglyph
See Eric Lippert's Events and Races article for further discussion of these issues.Dreamland
Can't edit as queue is full. This could do with updating r.e the "snapshot" variable, this can be avoided by using the question-dot ?. operator on the Invoke method. ?. enforces at a runtime level that the Invoke method will not be ran if the event has no subscribers.Cherubini
H
5

BeginInvoke() call immediatelly returns control to the calling thread and run a delegate in a separate thread from the ThreadPool, so this will be some kind of asynchronous execution.

Homo answered 11/8, 2011 at 9:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.