Handling the Tri-state asynchronous result dilemma
So in most programming languages and environments you have will define an operation that performs some type of interesting work. Let's call that ReallyInterestingMethod()™. ReallyInterestingMethod accepts any number of inputs and returns exactly one output (void or value as some type). The language contract (if supported i.e. Java) or documentation will also state the number and types of possible exceptions that can be raised by this operation. So when I want to call this example code, programmers tend to perform this type of logic:
try
{
var myResult = ReallyInterestingMethod();
}
catch (ReallyInterestingMethodException1)
{
//Do work appropriate here
}
catch (ReallyInterestingMethodException2)
{
//Do work appropriate here
}
No biggie there, we do this all the time in our code. But when we move to asynchronous work patterns (which frankly we need to get better at doing if we're ever going to leverage the power of multi-core processors to their fullest) we have a situation here. Do you see it? Let's see an example:
Func<MyResultType> doWork = ReallyInterestingMethod();
AsyncCallback result = ar => HandleReallyInterestingMethodResult(doWork.EndInvoke(ar));
doWork.BeginInvoke(result, null);
The issue here is that methods we write in imperative languages are actually tri-state! Executing-> Success | Failure. We just don't see this as the languages themselves hide this fact from the programmer. In blocking synchronous code you see that we have core language concepts supporting this success / fail by type dichotomy in the try...catch construct. But when we move into asynchronous code this language support goes AWOL-There's no support for the try catch. It's not that it cannot be done but just that there is no language construct to assist with this (yet… [evil grin]). Yes we can manually write this code inside that AsyncCallback but it's tiresome and ugly and sure as heck makes using a lambda out of the question as readability goes out the window. Example:
Func<MyResultType> doWork = ReallyInterestingMethod();
AsyncCallback result = ar =>
{
try
{
var myResult = doWork.EndInvoke(ar);
HandleReallyInterestingMethodResult(myResult);
}
catch (ReallyInterestingMethodException1)
{ /*Do work appropriate here*/ }
catch (ReallyInterestingMethodException2)
{ /*Do work appropriate here*/ }
};
doWork.BeginInvoke(result, null);
Ugly, huh? Now imagine if HandleReallyInterestingMethodResult() and the catch handlers are non-trivial? That's lots of logical operation code but that doesn't live together making maintenance expensive. The only alternative is lots of traditional methods where the readability is maintained but now our code is littered with methods only there for technical threading reasons. While we some really exciting and interesting prospects for the future (PLINQ, Rx Framework, Polyphonic Cord Based languages, and .Net 4.0's Task Parallel Library) what can we do for those programmers out there that have synchronous code that they want to slowly start dabbling in the asynchronous model without having to rewrite all their code? Well I hope in helping out for basic lightweight asynchronous operations you can leverage this pattern:
Func<MyResultType> doWork = MyResultType();
AsyncCallback result = ar => HandleReallyInterestingMethodResult(AsyncActionResult<MyResultType>.BuildUp(doWork.EndInvoke(ar)));
doWork.BeginInvoke(result, null);
… /*code omitted*/
private void HandleReallyInterestingMethodResult(AsyncActionResult<MyResultType> result)
{
try
{
var myResult = result;
/*Do work with the result*/
}
catch (ReallyInterestingMethodException1)
{ /*Do work appropriate here*/ }
catch (ReallyInterestingMethodException2)
{ /*Do work appropriate here*/ }
}
So did you catch that? It's subtle but instead of trying to handle this Tri-State dilemma in our callback, we can supply a collaborator that hides the state complexity. What it essentially allows is enabling us to defer the catch style logic to the asynchronous operation handler (in our example, HandleReallyInterestingMethodResult). While we unfortunately still don't have the ability to surface the exception without first calling for the results (maybe some IL Rewriting is called for here? Hrrmmmm… CECIL to the rescue! J) it sure does simplify the programming model for repeated boilerplate asynchronous handler code. So hopefully this will assist those of you out there that need to start dabbling in threading for scalability purposes but don't have the time to wait for all the new hotness not quite here yet OR that need a low barrier to entry and just need something "good enough" to get started.
Enjoy!
Jimmy Zimms is thinking that he can't wait for true 5th generation languages to get here |