Section 7.5. Asynchronous Events


7.5. Asynchronous Events

The most common use for delegates in .NET is for event subscription and publishing. Example 7-12 contains a simple definition of an event publisher and subscriber.

Example 7-12. Event publisher and subscriber, using synchronous event publishing
 using NumberChangedEventHandler = GenericEventHandler<int>; public class MyPublisher {    public event NumberChangedEventHandler NumberChanged;    public void FireEvent(int number)    {       if(NumberChanged != null)       {          NumberChanged(number);       }    } } public class MySubscriber {    public void OnNumberChanged(int number)    {...} }

Consider the following client code, which hooks subscribers to the publisher and fires the event:

     MyPublisher  publisher   = new MyPublisher(  );     MySubscriber subscriber1 = new MySubscriber(  );     MySubscriber subscriber2 = new MySubscriber(  );     publisher.NumberChanged += subscriber1.OnNumberChanged;     publisher.NumberChanged += subscriber2.OnNumberChanged;     publisher.FireEvent(3);

When a publisher fires an event, it's blocked until all the subscribers have finished handling the event, and only then does control return to the publisher. Disciplined and well-behaved subscribers should not perform any lengthy operations in their event-handling method, because that prevents other subscribers from handling the event (not to mention blocking the publisher). The problem is, how does the publisher know if it's dealing with disciplined subscribers? The reality is, of course, that the publisher can't tell. The publisher must therefore defensively assume that all subscribers are undisciplined. I call this the undisciplined subscriber problem. The solution to it is to fire the events asynchronously.

One option is to implement an asynchronous event firing by publishing the event on a worker thread. The problem with using a worker thread is that although it allows for asynchronous publishing, the publishing is serialized, because the subscribers are notified one at a time. The proper solution is to use threads from a thread pool and try to publish to each subscriber on a different thread. This isolates the undisciplined subscribers and allows concurrent and asynchronous publishing. At this point, you may be wondering why you can't simply use the built-in support delegates have for asynchronous invocation using threads from the pool. For example, the publisher in Example 7-12 could add a new method called FireEventAsync( ) and use BeginInvoke( ) to publish asynchronously:

     public void FireEventAsync(int number)     {        if(NumberChanged != null)        {           //Likely to raise exception:           NumberChanged.BeginInvoke(number,null,null);        }     }

Unfortunately, you can't call BeginInvoke( ) on the event member directly, because BeginInvoke( ) can be invoked only if the delegate's internal list of target methods contains just one target. As stated earlier in this chapter, if the delegate has more than one target in its list, an exception of type ArgumentException will be thrown. The workaround is to iterate over the delegate's internal invocation list, calling BeginInvoke( ) on each target in the list. Chapter 6 demonstrated how to access that list using the delegate's GetInvocationList( ) method:

     public virtual Delegate[] GetInvocationList(  );

GetInvocationList( ) returns a collection of delegates; each corresponds to a single target sink method, and therefore you can call BeginInvoke( ) on these delegates. Example 7-13 shows the implementation of the FireEventAsync( ) method. Note that you have to downcast the individual delegates to the actual delegate type. Note also that when you publish events asynchronously, the publisher has no use for return values or output from the event handlers. Therefore, EndInvoke( ) isn't used, and the publisher passes null for the last two parameters of BeginInvoke( ).

Example 7-13. Firing events asynchronously
 using NumberChangedEventHandler = GenericEventHandler<int>; public class MyPublisher {    public event NumberChangedEventHandler NumberChanged;    public void FireEventAsync(int number)    {       if(NumberChanged == null)       {          return;       }       Delegate[] delegates = NumberChanged.GetInvocationList(  );       foreach(Delegate del in delegates)       {          NumberChangedEventHandler sink = (NumberChangedEventHandler)del;          sink.BeginInvoke(number,null,null);       }    } }

There is a problem with the technique in Example 7-13, however: the code is coupled to the delegate type you invoke. You have to repeat such code in every case where you want to publish events asynchronously for every delegate type.

7.5.1. Asynchronous EventsHelper

Fortunately, you can compensate for the problem just described. Example 7-14 shows the FireAsync( ) method of the EventsHelper static class, which automates asynchronous defensive event publishing.

Example 7-14. Automating asynchronous event publishing with EventsHelper
 public static class EventsHelper {    delegate void AsyncFire(Delegate del,object[] args);    public static void FireAsync(Delegate del,params object[] args)    {       if(del == null)       {          return;       }       Delegate[] delegates = del.GetInvocationList(  );       AsyncFire asyncFire = InvokeDelegate;       foreach(Delegate sink in delegates)       {          asyncFire.BeginInvoke(sink,args,null,null);       }    }    static void InvokeDelegate(Delegate del,object[] args)    {       del.DynamicInvoke(args);    }    //Synchronous publishing methods, discussed in Example 6-11    public static void Fire(...)    {...} }

The technique shown in Example 7-14 is similar to that presented in Example 6-10: you use the params modifier to pass in any collection of arguments, as well as the delegate containing the subscriber list. The FireAsync( ) method iterates over the internal collection of the passed-in delegate. For each delegate in the list, it uses another delegate of type AsyncFire to asynchronously call the private helper method InvokeDelegate( ). InvokeDelegate( ) simply uses the DynamicInvoke( ) method of the Delegate type to invoke the delegate. Using EventHelper to publish events asynchronously is easy when compared to Example 7-13:

     using NumberChangedEventHandler = GenericEventHandler<int>;     public class MyPublisher     {        public event NumberChangedEventHandler NumberChanged;        public void FireEventAsync(int number)        {           EventsHelper.FireAsync(NumberChanged,number);        }     }

When using EventsHelper from Visual Basic 2005, you need to use the matching compiler-generated member variable suffixed with Event, as shown in Chapter 6.


7.5.1.1 Type-safe asynchronous EventsHelper

As with Example 6-10, the problem with the AsyncFire( ) method of Example 7-14 is that it is not type-safe. AsyncFire( ) uses an array of objects, and any mismatch in the number and type of arguments will not be discovered at compile time. At runtime, the use of BeginInvoke( ) on the helper method InvokeDelegate( ) will suppress any error propagation, and you will not be aware of the problem. The solution is identical to the one used in the synchronous case: have EventsHelper combine AsyncFire( ) with GenericEventHandler, and commit to using GenericEventHandler (which is a good idea in general). The compiler will correctly infer the delegate type to use and enforce type safety with the number and type of the arguments. The type-safe asynchronous version of EventsHelper is shown in Example 7-15.

Example 7-15. Type-safe asynchronous publishing with EventsHelper
 public static class EventsHelper {    delegate void AsyncFire(Delegate del,object[] args);    //Same as FireAsync(  ) in Example 7-14    public static void UnsafeFireAsync(Delegate del,params object[] args)    {...}    public static void FireAsync(GenericEventHandler del)    {       UnsafeFireAsync(del);    }    public static void FireAsync<T>(GenericEventHandler<T> del,T t)    {       UnsafeFireAsync(del,t);    }    public static void FireAsync<T,U>(GenericEventHandler<T,U> del,T t,U u)    {       UnsafeFireAsync(del,t,u);    }    public static void FireAsync<T,U,V>(GenericEventHandler<T,U,V> del,T t,U u,V v)    {       UnsafeFireAsync(del,t,u,v);    }    public static void FireAsync<T,U,V,W>(GenericEventHandler<T,U,V,W> del,                                          T t,U u,V v,W w)    {       UnsafeFireAsync(del,t,u,v,w);    }    public static void FireAsync<T,U,V,W,X>(GenericEventHandler<T,U,V,W,X> del,                                            T t,U u,V v,W w,X x)    {       UnsafeFireAsync(del,t,u,v,w,x);    }    public static void FireAsync<T,U,V,W,X,Y>(GenericEventHandler<T,U,V,W,X,Y> del,                                              T t,U u,V v,W w,X x,Y y)    {       UnsafeFireAsync(del,t,u,v,w,x,y);    }    public static void FireAsync<T,U,V,W,X,Y,Z>(GenericEventHandler<T,U,V,W,X,Y,Z> del,                                                T t,U u,V v,W w,X x,Y y,Z z)    {       UnsafeFireAsync(del,t,u,v,w,x,y,z);    }    public static void FireAsync(EventHandler del,object sender,EventArgs e)    {       UnsafeFireAsync(del,sender,e);    }    public static void FireAsync<E>(EventHandler<E> del,object sender,E e)                                                                 where E : EventArgs    {       UnsafeFireAsync(del,sender,e);    }    //Rest same as Example 7-14 }

If you can only commit to using EventHandler, Example 7-15 contains type-safe support for it as well. The non-type-safe method UnsafeFireAsync( ) of EventsHelper is still available when you cannot use GenericEventHandler or EventHandler, and the lack of type safety with this approach is a small price to pay compared with the problem of Example 7-13.

: .NET Queued Components

.NET's built-in support for asynchronous method invocation standardizes asynchronous calls and saves you writing a lot of error-prone plumbing code. Asynchronous calls in enterprise applications, however, often require additional support, such as for disconnected work, error handling, auto-retry mechanisms, and transaction support. For such cases, .NET provides an advanced asynchronous call mechanism called queued components as part of its .NET Enterprise Services (System.EnterpriseServices namespace). Queued components use the Microsoft Message Queue (MSMQ) to queue asynchronous calls and transport them to the target server component. A discussion of .NET queued components is beyond the scope of this book, but you can read about them in Chapters 8 and 10 of my book COM and .NET Component Services (O'Reilly).




Programming. NET Components
Programming .NET Components, 2nd Edition
ISBN: 0596102070
EAN: 2147483647
Year: 2003
Pages: 145
Authors: Juval Lowy

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net