Converting A Synchronous Program Into An Asynchronous Program
Introduction
One of the challenges in writing programs in today’s RIA environments (Javascript, Flex, Silverlight and GWT) is expressing the flow of control between multiple asynchronous XHR calls. A “one-click-one-XHR” policy is often best, but you don’t always have control over your client-server protocols. A program that’s simple to read as a synchronous program can become a tangle of subroutines when it’s broken up into a number of callback functions. One answer is program translation: to manually or automatically convert a synchronous program into an asynchronous program: starting from the theoretical foundation, this article talks about a few ways of doing that.
Thibaud Lopez Schneider sent me a link to an interesting paper he wrote, titled “Writing Effective Asynchronous XmlHttpRequests.” He presents an informal proof that you can take a program that uses synchronous function calls and common control structures such as if-else and do-while, and transform it a program that calls the functions asynchronously. In simple language, it gives a blueprint for implementing arbitrary control flow in code that uses asynchronous XmlHttpRequests.
In this article, I work a simple example from Thibaud’s paper and talk about four software tools that automated the conversion of conventional control flows to asynchronous programming. One tool, the Windows Workflow Foundation, lets us compose long-running applications out of a collection of asynchronous Activity objects. Another two tools are jwacs and Narrative Javascript, open-source translators that translated pseudo-blocking programs in a modified dialect of JavaScript into an asynchronous program in ordinary JavaScript that runs in your browser.
A simple example: Sequential Execution
I’m going to lift a simple example from Thibaud’s paper, the case of sequential execution. Imagine that we want to write a function f(), that follows the following logic
[01] function f() { [02] ... pre-processing ... [03] result1=MakeRequest1(argument1); [04] ... think about result1 ... [05] result2=MakeRequest2(argument2); [06] ... think about result2 ... [07] result3=MakeRequest3(argument3); [08] ... think about result3 ... [09] return finalResult; [10] }
where functions of the form MakeRequestN are ordinary synchronous functions. If, however, we were working in an environment like JavaScript, GWT, Flex, or Silverlight, server requests are asynchronous, so we’ve only got functions like:
[11] function BeginMakeRequestN(argument1, callbackFunction);
It’s no longer possible to express a sequence of related requests as a single function, instead we need to transform f() into a series of functions, like so
[12] function f(callbackFunction) { [13] ... pre-processing ... [14] BeginMakeRequest1(argument,f1); [15] } [16] [17] function f1(result1) { [18] ... think about result1 ... [19] BeginMakeRequest2(argument2,f2); [20] } [21] [22] function f2(result2) { [23] ... think about result2 ... [24] BeginMakeRequest3(argument3,f3); [25] } [26] [27] function f3(result3) { [28] ... think about result 3 ... [29] callbackFunction(finalResult); [30] }
My example differs from the example of on page 19 of Thibaud’s paper in a few ways… In particular, I’ve added the callbackFunction that f() uses to “return” a result to the program that calls it. Here the callbackFunction lives in a scope that’s shared by all of the fN functions, so it’s available in f3. I’ve found that when you’re applying Thibuad’s kind of thinking, it’s useful for f() to correspond to an object, of which the fN() functions are methods. [1] [2] [3]
Thibaud also works the implementation of if-then-else, switch, for, do-while, parallel-do and other common patterns — read his paper!
What next?
There are things missing from Thibaud’s current draft: for instance, he doesn’t consider how to implement exception handling in asynchronous applications, although it’s quite possible to do.
Thinking about things systematically helps you do things by hand, but it really comes into it’s own when we use systematic thinking to develop tools. I can imagine two kinds of tools based on Thibaud’s ideas:
- Specialized languages for expressing asynchronous flows, and
- Compilers that transform synchronous programs to asynchronous programs
Windows Workflow Foundation
Windows Workflow Foundation is an example of the first approach.
Although it’s not designed for use in asynchronous RIA’s, Microsoft’s Windows Workflow Foundation is an new approach to writing reactive programs. Unfortunately, like a lot of enterprise technologies, WWF is surrounded by a lot of hype that obscures a number of worthwhile ideas: the book Essential Windows Workflow Foundation by Shukla and Schmidt is a lucid explanation of the principles behind it. It’s good reading even if you hate Microsoft and would never use a Microsoft product, because it could inspire you to implement something similar in your favorite environment. (I know someone who’s writing a webcrawler in PHP based on a similar approach)
What does it do?
In WWF, you create an asynchronous program by composing a set of asynchronous Activities. Ultimately your program is a tree of Activity objects that you can assemble any way you like, but typically you’d build them with a XAML (XML) file that might look like
[31] <Interleave x:Name="i1"> [32] <Sequence x:Name="s1"> [33] <ReadLine x:Name="r1" /> [34] <WriteLine x:Name="w1" [35] Text="{wf:ActivityBind r1,path=Text}" /> [36] <ReadLine x:Name="r2" /> [37] <WriteLine x:Name="w2" [38] Text="{wf:ActivityBind r2,path=Text}" /> [39] </Sequence> [40] <Sequence x:Name="s2"> [41] <ReadLine x:Name="r3" /> [42] <WriteLine x:Name="w3" [43] Text="{wf:ActivityBind r3,path=Text}" /> [44] <ReadLine x:Name="r4" /> [45] <WriteLine x:Name="w4" [46] Text="{wf:ActivityBind r4,path=Text}" /> [47] </Sequence> [48] </Interleave>
(The above example is based on Listing 3.18 on Page 98 of Shukla and Schmidt, with some namespace declarations removed for clarity)
This defines a flow of execution that looks like:
The <Interleave> activity causes two <Sequence> activities to run simultaneously. Each <Sequence>, in turn, sequentially executes two alternating pairs of <ReadLine> and <WriteLine> activities. Note that the attribute values that look like {wf: ActivityBind r3,path=Text} wire out the output of a <ReadLine> activity to the input of a <WriteLine> activity.
Note that <Interleave>, <Sequence>, <ReadLine> and <WriteLine> are all asynchronous activities defined by classes Interleave, Sequence, ReadLine And WriteLine that all implement Activity. An activity can invoke other activities, so it’s possible to create new control structures. Activities can wait for things to happen in the outside world (such as a web request or an email message) by listening to a queue. WWF also defines an elaborate model for error handling.
Although other uses are possible, WWF is intended for the implementation of server applications implementations that implement workflows. Imagine, for instance, a college applications system, which must wait for a number of forms from the outside, such as
- an application,
- standardized test scores, and
- letters of reccomendation
and that needs to solicit internal input from
- an initial screening committee,
- the faculty of individual departments, and
- the development office.
The state of a workflow can be serialized to a database, so the workflow can be something that takes place over a long time, such as months or weeks — multiple instances of the workflow can exist at the same time.
WWF looks like a fun environment to program for, but I don’t know if I’d trust it for a real business application. Why? I’ve been building this sort of application for years using relational databases, I know that it’s possible to handle the maintenance situations that occur in real life with a relational representation: both the little tweaks you need to make to a production system from time to time, and the more major changes required when your process changes. Systems based on object serialization, such as WWF, tend to have trouble when you need to change the definition of objects over time.
I can say, however, that the Shukla and Schmidt book is so clear that an ambitious programmer could understand enough of the ideas behind WWF to develop a similar framework that’s specialized for developing asynchronous RIAs in Javascript, Java, or C# pretty quickly. Read it!
Transforming Javascript and Other Languages
Another line of attack on asynchronous programming is the creation of compilers and translators that transform a synchronous program into a synchronous program. This is particularly popular in Javascript, where open-source tools such as jwacs (Javascript With Advanced Continuation Syntax) let you write code like this:
[49] function main() { [50] document.getElementById('contentDiv').innerHTML = [51] '<pre>' [52] + JwacsLib.fetchData('GET', 'dataRows.txt') [53] + '</pre>'; [54] }
Jwacs adds four new keywords to the Javascript language: internally, it applies transformations like the ones in the Thibaud paper. Although it looks as if the call to JwacsLib.fetchData blocks, in reality, it splits the main() function into two halves, executing the function by a form of cooperative multitasking.
Narrative Javascript is a similar open-source translator that adds ->, a “yielding” operator to Javascript. This signals the translator to split the enclosing function, and works for timer and UI event callbacks as well as XHR. Therefore, it’s possible to write a pseudo-blocking sleep() function like:
[55] function sleep(millis) { [56] var notifier = new EventNotifier(); [57] setTimeout(notifier, millis); [58] notifier.wait->(); [59] }
Narrative Javascript doesn’t remember the special nature of the the sleep() function, so you need to call it with the yielding operator too. With it, you can animate an element like so:
[60] for (var i = 0; i < frameCount - 1; i++) { [61] var nextValue = startValue + (jumpSize * i); [62] element.style[property] = nextValue + "px"; [63] sleep->(frequency); [64] }
You can use the yielding operator to wait on user interface events as well. If you first define
[65] function waitForClick(element) { [66] var notifier = new EventNotifier(); [67] element.onclick = notifier; [68] notifier.wait->(); [69] }
you can call it with the yielding operator to wait for a button press
[70] theButton.innerHTML = "go right"; [71] waitForClick->(theButton); [72] theButton.innerHTML = "-->"; [73] ... continue animation ...
The RIFE Continuation Engine implements something quite similar in Java, but it translates at the bytecode level instead of at the source code level: it aims to transform the server-side of web applications, rather than the client, by allowing the execution of a function to span two separate http requests.
Conclusion
It’s possible to systematically transform a function that’s written in terms of conventional control structures and synchronous function calls into a collection of functions that performs the same logic using asynchronous function calls. A paper by Thibaud Lopez Schneider points the way, and is immediately useful for RIA programmers that need to convert conventional control structures in their head into asynchronous code.
A longer-term strategy is to develop frameworks and languages that make it easier to express desired control flows for asynchronous program. The Windows Workflow Foundation from Microsoft is a fascinating attempt to create a specialized language for assembling asynchronous programs from a collection of Activity objects. jwacs and Narrative Javascript are bold attempts to extend the Javascript language so that people can express asynchronous logic as pseudo-threaded programs. The RIFE Continuation Engine demonstrates that this kind of behavior can be implemented in more static languages such as Java and C#. Although none of these tools are ready for production-quality RIA work, they may lead to something useful in the next few years.
Paul Houle on August 13th 2008 in Asynchronous Communications, GWT, Silverlight