Subscribe:
|
Categories:
ASP.NET
ast_mono
Asterisk
Code
F#
FreeSWITCH
Guatemala
Humour
IL
Korean
Mei
Misc
Misc. Technology
Personal
Photography
Security
Spammers
VoIP
Sign In
[Giagnocavo]Michael::Write()
Thursday, August 28, 2008
Tsk, tsk, Silverlight - Events are not async's friend
OK, so Silverlight 2 is still in Beta 2 and hopefully will have time to change. But I would have thought this fix would have gotten into Beta 2 (as far as I can tell, it has not). At any rate, it applies to async design in general.
Scenario:
You are writing an async method and need to call code when your async process finishes. How do you expose this to your caller? For some reason (I'd love to know what it is), it seems to be getting more popular to expose an event to accomplish this.
Events suck:
Let's use a simple case. You want to get two URLs, using the return of one to get the other, then print the result to the console. You have a few local variables you need to use as well. Let's see what it'd look like without async:
// Inside some method:
var url1 = Console.ReadLine();
var url2 = Console.ReadLine();
var someData = Console.ReadLine();
// onEx = some exception handler
try {
var res1 = webThingy.Download(url1);
var res2 = webThingy.Download(url2
+ "?data=" + res1.Data);
Console.WriteLine(someData + res2);
} catch (Exception ex) {
onEx(ex);
}
Straightforward, eh? Now, with async + events? You _could_ go create a new object type and add all sorts of fields and methods and whatnot, but that's a lot of work and gets ugly quickly. Closures are a natural help here. So how does the event-based async code look?
// Inside some method:
var url1 = Console.ReadLine();
var url2 = Console.ReadLine();
var someData = Console.ReadLine();
// onEx = some exception handler
OnDownloadCompleteEventHandler first;
webThingy.OnDownloadComplete += first = (o, res1) => {
if (res1.Exception != null) {
onEx(res1.Exception);
return;
}
webThingy.OnDownloadComplete -= first;
webThingy.OnDownloadComplete += (o2, res2) => {
if (res2.Exception != null) {
onEx(res2.Exception);
return;
}
try {
Console.WriteLine(someData + res2.Data);
} catch (Exception ex) { onEx(ex); }
}
try {
webThingy.DownloadAsync(url2 + "?data=" + res1.Data);
} catch (Exception ex) { onEx(ex); }
};
try {
webThingy.DownloadAsync(url1);
} catch (Exception ex) { onEx(ex); }
I think this judges itself.
Better
The way it SHOULD be is that any async method should take two arguments, one to call for result, one for exception. Let's see how that would look:
// Inside some method:
var url1 = Console.ReadLine();
var url2 = Console.ReadLine();
var someData = Console.ReadLine();
// onEx = some exception handler
webThingy.DownloadAsync(url1, onEx, res1 => {
webThingy.DownloadAsync(url2 + ?data=" + res1, onEx, res2 => {
Console.WriteLine(someData + res2);
});
});
This code isn't perfect, but it's sure a ton better than the event-based system. With a bit extra work, you could build a simple async framework. Every Async method could return an Async object that would allow you to consolidate stuff like exception handling and cancellation. But even without that, this code straight away is much superior.
The extra downside
Interestingly enough, .NET 1 had the concept of BeginXXX/EndXXX, but because there were no closures, it was always a bit more of a pain to implement. BeginXXX/EndXXX, while not making exception handling as easy, are at least a good start.
The cool thing about BeginXXX/EndXXX was that you could refactor them generically into nice async syntax. Heres a quick and dirty example [F#'s async stuff works similarly.]:
static Action<A1, Action<Exception>, Action<R>> ToAsync<A1, R>(
Func<A1, AsyncCallback, object, IAsyncResult> begin,
Func<IAsyncResult, R> end) {
return (arg, onEx, cont) => {
begin(arg, iar => {
try { cont(end(iar)); } catch (Exception ex) { onEx(ex); }
}, null);
};
}
static void BeginAcceptAsync(this Socket s, int timeout,
Action<Exception> onEx, Action<Socket> cont) {
ToAsync<int, Socket>(s.BeginAccept, s.EndAccept)(timeout, onEx, cont);
}
static void Main() {
Socket s = ...;
s.BeginAcceptAsync(10,
ex => Console.WriteLine(ex),
sock => sock.Close());
}
(Yes, I think this is a legitimate use of type extensions, but again, only because the original library had a design flaw. And even the non-extension syntax wouldn't necesarily be bad.)
But, to my knowledge, since there's no way to reference an event, this is not possible with the async + event approach t hat is becoming all the rage (yes, SocketAsyncEventArgs, I'm talking about you too). If you know of a way to ease the pain of async+events,
tell me
.
In summary
It seems that events are a bad choice for code that is not loosely coupled, such as UIs. Even when loosely coupled, sometimes a simple delegate field would be a better choice, since you can compose (like tacking on a filter or otherwise augmenting the callback). But in the case of async, I cannot see how it is good.
Code
Thursday, August 28, 2008 1:48:31 AM UTC
Comments [1]
|
Trackback
Thursday, August 28, 2008 7:44:22 AM UTC
I fully agree with you. I absolutely detest the new event-based async model. From my point of view, it seems to be designed for the same VB crowd that mix up their business logic and UI logic, and for the same ASP.NET webforms crowd that mix up their business logic and UI logic.
In other words, it lets some folks get that hard async IO stuff working in a component you plop on a form without having to think about complicated things like delegates.
Barry Kelly
|
barry dot j dot kellyAT NOSPAMgmail dot com
Name
E-mail
Home page
Remember Me
Comment (HTML not allowed)
Enter the code shown (prevents robots):
Live Comment Preview