Warning – long post with a lot of sketched code. Largely just me “thinking aloud”.
I’d been reading this post about how events are a bad mechanism to use for the asynchronous programming model in Silverlight and then this follow up post.
This reminded me a little bit ( i.e. it’s not the exact same situation ) of the approach that I ended up in taking to dealing with Storyboards in Silverlight at one point when I was playing with earlier bits.
I don’t necessarily disagree with what’s been written although I disagree with the 2nd poster’s hatred of asynchronous code. For me, 2 of the constant “UI nightmares” that seem to crop up are;
- The blocking method call that the programmer never thought would last more than sub-second.
- The transparent network call where the remote resource is made to transparently behave like a local resource.
For me, these 2 can combine to kill a lot of UI software and they show up in many applications every day and so if forcing async on to programmers makes this any better then I’m happy.
Anyway, back to this idea of events.
Let’s say that I want to download 2 pages using WebClient and work out their total size in bytes – this is full Framework 3.5 Sp1 code rather than Silverlight code but it’s meant to be similar to the code in the first post referenced above ( keeping in the multiple return statements and so on which isn’t something I usually write );
static void Main(string[] args) { WebClient client = new WebClient(); int totalSize = 0; DownloadDataCompletedEventHandler handler1 = null; handler1 = (s1, e1) => { client.DownloadDataCompleted -= handler1; if (e1.Error != null) { HandleException(e1.Error); return; } totalSize += e1.Result.Length; DownloadDataCompletedEventHandler handler2 = null; handler2 = (s2, e2) => { client.DownloadDataCompleted -= handler2; if (e2.Error != null) { HandleException(e2.Error); return; } totalSize += e2.Result.Length; Console.WriteLine("Total Size {0}", totalSize); }; client.DownloadDataCompleted += handler2; client.DownloadDataAsync(new Uri("http://www.apple.com")); }; client.DownloadDataCompleted += handler1; client.DownloadDataAsync(new Uri("http://www.microsoft.com")); Console.ReadLine(); } static void HandleException(Exception ex) { Console.WriteLine("Error occurred {0}", ex.Message); }
This isn’t very nice code and it’s clearly repetitive in the sense that I seem to go through the very same process twice so it leaves me feeling a bit bad. Surely there’s some way to make this better? The essential pattern seems to be;
- Sync some “completed” event handler.
- Begin some operation.
- Handler the completed event;
- Unsync the event handler
- Check for errors – having a base class here AsyncCompletedEventArgs might prove to be very useful.
- Proceed to execute the next bit of code if we didn’t get any error, otherwise invoke the error handler.
However, in trying to make this code neater, I actually made it worse 🙂
One of the things that I struggle with in C# is the restriction that I can’t pass an event into a function. Being able to pass an event into a function would really help in being able to deal with step 1 above.
As a way of illustrating this, imagine I’ve got some class T and I want to write a function that can sync up to some event E of a particular type ( let’s say EventHandler ) on instances of T.
I’m not sure how I can do that? That is, if I’ve got;
class T { public event EventHandler E; }
and if I’ve got a suitable Handler somewhere;
class Program { static void Handler(object s, EventArgs e) { }
then how can I write a function which will add Handler as an event handler to the event E on an instance of T. That is, I want to write something like;
class Program { static void Main(string[] args) { T t = new T(); AddHandler(t, t.E, Handler); // This is what I want. } static void AddHandler<T>(T t, MulticastDelegate d, EventHandler handler) { } static void Handler(object s, EventArgs e) { }
but I’m not allowed to pass an event into a function so I can’t make that kind of call. I could do something like;
class Program { static void Main(string[] args) { T t = new T(); AddHandler(t, x => x.E += Handler); } static void AddHandler<T>(T t, Action<T> addAction) { } static void Handler(object s, EventArgs e) { }
but that’s not really doing it for me as the caller is essentially writing the code I want to hide in my function. Reflection might help?
class Program { static void Main(string[] args) { T t = new T(); AddHandler(t, "E", Handler); } static void AddHandler<T>(T t, string eventName, EventHandler handler) { t.GetType().GetEvent(eventName).AddEventHandler(t, handler); } static void Handler(object s, EventArgs e) { }
Also, I need to be a bit careful because that won’t work for instance methods and if we defined E using some kind of derived type such as DownloadDataCompletedEventHandler then that code will stop working and what we actually then need is;
class Program { static void Main(string[] args) { T t = new T(); AddHandler(t, "E", Handler); } static void AddHandler<T>(T t, string eventName, EventHandler handler) { EventInfo ei = t.GetType().GetEvent(eventName); ei.AddEventHandler(t, Delegate.CreateDelegate(ei.EventHandlerType, handler.Target, handler.Method)); } static void Handler(object s, EventArgs e) { }
That’s a bit “better”. It’s not exactly what I’d wanted but it does the job to some extent. Factoring that out into its own class gives me;
public static class EventHelper { public static Delegate AddHandler(object source, string eventName, Delegate handler) { EventInfo ei = GetEventInfo(source, eventName); Delegate eh = Delegate.CreateDelegate( ei.EventHandlerType, handler.Target, handler.Method); ei.AddEventHandler(source, eh); return (eh); } public static void RemoveHandler(object source, string eventName, Delegate handler) { EventInfo ei = GetEventInfo(source, eventName); Delegate eh = Delegate.CreateDelegate( ei.EventHandlerType, handler.Target, handler.Method); ei.RemoveEventHandler(source, eh); } private static EventInfo GetEventInfo(object source, string eventName) { return (source.GetType().GetEvent(eventName)); } }
Which doesn’t feel too great given all that reflection and so on but looks to just about do the job.
Experiment 1 – Trying to Manually Build a List of Actions
Going back to the original purpose, I can now write some kind of asynchronous action class that takes;
- The action to run ( i.e. some lambda )
- The object which fires an event when the action is over.
- The name of the event on the object in ( 2 ).
- The code to run when the action is completed.
I ended up writing all of this code that follows below.
Don’t fret too much about it because I actually think it’s worse than the original code that I had so I won’t be sticking with it and I’m not sure if all this reflection will work in Silverlight code anyway.
public interface IAsyncAction { void Begin(); event AsyncCompletedEventHandler Completed; } public class AsyncActionList : IAsyncAction { public AsyncActionList(params IAsyncAction[] actions) { this.actions = new List<IAsyncAction>(actions); } public void Begin() { if (index <= actions.Count) { IAsyncAction action = actions[index]; action.Completed += CompletedHandler; action.Begin(); } } void CompletedHandler(object sender, AsyncCompletedEventArgs args) { actions[index].Completed -= CompletedHandler; if ((++index >= actions.Count) || (args.Error != null) || (args.Cancelled)) { if (Completed != null) { Completed(this, args); } } else { Begin(); } } int index = 0; List<IAsyncAction> actions; public event AsyncCompletedEventHandler Completed; } public class AsyncAction : IAsyncAction { public AsyncAction(Action actionToTake, object eventSource, string eventName, AsyncCompletedEventHandler successHandler) { this.eventSource = eventSource; this.eventName = eventName; this.action = actionToTake; this.successHandler = successHandler; if (successHandler != null) { Completed += successHandler; } } public void Begin() { EventHelper.AddHandler(eventSource, eventName, new AsyncCompletedEventHandler(Handler)); action(); } private void Handler(object sender, AsyncCompletedEventArgs args) { EventHelper.RemoveHandler(eventSource, eventName, new AsyncCompletedEventHandler(Handler)); if (Completed != null) { Completed(this, args); } if (successHandler != null) { Completed -= successHandler; } } public event AsyncCompletedEventHandler Completed; object eventSource; string eventName; Action action; AsyncCompletedEventHandler successHandler; } class Program { static void Main(string[] args) { WebClient client = new WebClient(); int totalLength = 0; AsyncActionList actions = new AsyncActionList( new AsyncAction( () => client.DownloadDataAsync(new Uri("http://www.microsoft.com")), client, "DownloadDataCompleted", (s, e) => totalLength += ((DownloadDataCompletedEventArgs)e).Result.Length), new AsyncAction( () => client.DownloadDataAsync(new Uri("http://www.apple.com")), client, "DownloadDataCompleted", (s, e) => totalLength += ((DownloadDataCompletedEventArgs)e).Result.Length)); actions.Completed += (s, e) => { if (e.Error != null) { Console.WriteLine(e.Error.Message); } else { Console.WriteLine(totalLength); } }; actions.Begin(); Console.ReadLine(); } }
Clearly, I’m trying ( and struggling ) to make the Main() routine a little more declarative by building up this AsyncActionList and then just calling Begin on that list but the mess that I’ve got into in trying to wait for unknown event handlers and all the Lambdas are just making the code more obfuscated than it originally was.
Experiment 2 – A Different Approach with an Extension Method and a “Pipeline” of Actions
I wondered whether extension methods might help at all here. I had a sort of sketch in my mind that looks something like;
but that’s just pseudo-code and left me wondering a lot what the extension methods would need to look like in order to make that work and whether they’d be so convoluted as to make the whole thing uglier than it was in the first place.
I went a little way with it in that I wrote a bunch of classes ( note – there are various race conditions and thread safety problems with these classes – I haven’t really finished them );
public abstract class ActionEntry { public abstract void Invoke(); public object InvokingObject { get; set; } public AsyncCompletedEventArgs InvokingArgs { get; set; } public virtual bool WaitsForEvent { get { return (waits); } set { waits = value; } } private bool waits; } public class ActionExecuteCodeEntry<T,E> : ActionEntry where E : AsyncCompletedEventArgs { public ActionExecuteCodeEntry(Action<T,E> action) { this.action = action; } public override void Invoke() { action((T)InvokingObject, (E)InvokingArgs); } private Action<T, E> action; } public abstract class AsyncActionEntry : ActionEntry { protected virtual void FireCompleted(object sender, AsyncCompletedEventArgs args) { if (Completed != null) { Completed(sender, args); } } public EventHandler<AsyncCompletedEventArgs> Completed; } public class AsyncEventWaitActionEntry<T, E> : AsyncActionEntry { public AsyncEventWaitActionEntry(T eventSource, string eventName) { this.eventSource = eventSource; this.eventName = eventName; } public override void Invoke() { EventHelper.AddHandler(eventSource, eventName, new AsyncCompletedEventHandler(Handler)); } private void Handler(object source, AsyncCompletedEventArgs args) { EventHelper.RemoveHandler(eventSource, eventName, new AsyncCompletedEventHandler(Handler)); FireCompleted(this, args); } T eventSource; string eventName; } public class AsyncActionList<T,E> where E : AsyncCompletedEventArgs { public AsyncActionList(T sender) { currentObject = sender; actions = new Queue<ActionEntry>(); } public AsyncActionList<T,E> AddEventWait(string eventName) { actions.Enqueue(new AsyncEventWaitActionEntry<T, E>(currentObject, eventName)); return (this); } public AsyncActionList<T,E> AddImmediate(Action<T, E> action) { actions.Enqueue(new ActionExecuteCodeEntry<T, E>(action)); return (this); } public AsyncActionList<T, E> AddAfterEvent(Action<T, E> action) { actions.Enqueue(new ActionExecuteCodeEntry<T, E>(action) { WaitsForEvent = true }); return (this); } public AsyncActionList<T, E> AddErrorHandler(Action<T, AsyncCompletedEventArgs> handler) { this.errorHandler = handler; return (this); } public void Execute() { bool async = false; while (!async && (actions.Count > 0)) { ActionEntry entry = actions.Peek(); if (async = entry.WaitsForEvent) { entry.WaitsForEvent = false; } else { actions.Dequeue(); if (entry is AsyncActionEntry) { ((AsyncActionEntry)entry).Completed += Handler; } entry.InvokingObject = currentObject; entry.InvokingArgs = currentEventArgs; entry.Invoke(); } } } private void Handler(object sender, AsyncCompletedEventArgs args) { ((AsyncActionEntry)sender).Completed -= Handler; currentEventArgs = args; if ((args.Error != null) && (errorHandler != null)) { errorHandler(currentObject, currentEventArgs); } else { Execute(); } } T currentObject; Action<T, AsyncCompletedEventArgs> errorHandler; AsyncCompletedEventArgs currentEventArgs; Queue<ActionEntry> actions; } public static class ActionListExtensions { public static AsyncActionList<T, E> BeginEventWait<E, T>(this T source, string eventName) where E : AsyncCompletedEventArgs { AsyncActionList<T, E> actions = new AsyncActionList<T, E>(source); actions.AddEventWait(eventName); return (actions); } }
The essential idea here is to take a bunch of code that either;
- Registers interest in some event on some object.
- Executes immediately
- Waits for a previous event to fire before executing.
This allows me to write my previous Main routine as;
class Program { static void Main(string[] args) { WebClient client = new WebClient(); int totalSize = 0; client. BeginEventWait<DownloadDataCompletedEventArgs, WebClient>("DownloadDataCompleted"). AddErrorHandler((c, e) => { Console.WriteLine("Errored {0}", e.Error.Message); }). AddImmediate((c, e) => c.DownloadDataAsync(new Uri(http://www.microsoft.com))). AddAfterEvent((c, e) => { totalSize += e.Result.Length; }). AddEventWait("DownloadDataCompleted"). AddImmediate((c, e) => c.DownloadDataAsync(new Uri("http://www.apple.com"))). AddAfterEvent((c, e) => { totalSize += e.Result.Length; }). AddImmediate((c,e) => { Console.WriteLine(totalSize); }). Execute(); Console.ReadLine(); } }
which actually doesn’t read too badly to me.
Essentially, it’s trying to take a series of function calls and turn them into a list of items to be processed in sequence, some of them are just simple lambda executions ( AddImmediate ), some of them involve registering a wait for a particular event ( BeginEventWait/AddEventWait ) and some of them involve waiting until the last event in the “pipeline” has fired before executing ( AddAfterEvent ). There’s a slightly feeble attempt to pass the original object ( in this case the WebClient instance ) and the current AsyncCompletedEventArgs along the “pipeline” in order that the next step in that pipeline can pick it up and do something with it as I do by using the Result.Length property in both cases.
At the end of all this, I think I’d have to either;
- Burn a tonne of time thinking hard about what I’d want these classes/extensions to really look like in order to make what I produce better than the very original example at the top of the post.
- Accept that whilst the original example at the top of the post isn’t pretty it’s at least using standard .NET techniques whereas my other 2 attempts are using my own libraries which probably obfuscate the whole thing more than they clarify it. They also probably don’t work and would need a tonne more effort to be truly re-usable.
I give up for now 🙂