TL;DR;
CoThread lets you to create cooperative “threads” and coroutines in Actionscript 3 (Flex/AIR).
With CoThread, you can easily break up any task, including recursive functions, so that your UI stays responsive even while your application is doing significant processing. Asynchronous code that uses CoThread is easy to read and write.
CoThreadAS3 is hosted on GitHub and mirrored on BitBucket.
The below is the wordy behind-the-scenes version of the README file.
Motivation for CoThread
I have been looking for a way to write “threaded” Actionscript code for a while. All of the examples I have seen require you to manually break up your code into discreet blocks, and also require you to manually maintain the state of your operation between calls. Not only is this difficult and error-prone, it is extremely difficult to write recursive functions that can be arbitrarily paused and resumed.
To show how hard this is, imagine that you need to process every pixel in a large image:
function processAllPixels(pixels : Array) : void
{
var numPixels : int = pixels.length;
for (x=0; x<numPixels; ++x)
{
var pixel : int = pixels[x];
processPixel(pixel);
}
}
That’s fine, but you discover it takes 5 seconds to process all the pixels, and your boss doesn’t want the program to lock up while it’s working. Oh, and he wants a progress bar. Other examples I’ve seen told me to write a function that just processes some of the pixels with each call:
var whereLeftOffLastTime : int = 0;
static const int NUM_PIXELS_TO_PROCESS_EACH_FRAME = 500;
function continueProcessingPixels(pixels : Array) : Boolean
{
var isDoneYet : Boolean = false;
var numPixels : int = pixels.length;
var indexToStop = whereLeftOffLastTime + NUM_PIXELS_TO_PROCESS_EACH_FRAME;
if (indexToStop >= numPixels)
{
indexToStop = numPixels;
isDoneYet = true;
}
for (x=whereLeftOffLastTime; x<indexToStop; ++x)
{
var pixel : int = pixels[x];
processPixel(pixel);
}
whereLeftOffLastTime = indexToStop;
return isDoneYet;
}
Anyway, you get the basic idea. I wrote the above code in a readme file, so it’s probably got compile errors and logic errors, and I’m sure it could be cleaner, but that’s also kinda my point. Just to split a for loop, I find myself keeping track of a lot of extra state. It’s slow and error-prone.
After I familiarized myself with the above techniques, I decided to apply it to my XML file saving code. It took a long time to write my files to disk, and I wanted to break it up. There was just one problem: My XML saving code was recursive! If I want to make sure that my XML processor only ran for 30ms each call, I might want it to arbitrarily stop when it was 20 function calls deep. How would I save and restore that state? Let me give you a simple recursive example:
//Recursively pretty-prints XML, indenting each deeper level
public function printXML(xml : XML, indentString : String) : void
{
if ((xml == null) || (xml.name() == null))
{
return;
}
else
{
indentString += "\t";
_textWriter.writeLine(indentString + xml.name()+":"+xml.text());
for each(var child : XML in xml.children())
{
printXML(child, indentString);
}
_textWriter.writeLine(indentString + "/"+xml.name());
}
}
(Yeah, I know this could be written without recursion, but the code to save a file to XML could not easily be, and this is a simple recursive example to illustrate my point.)
So imagine that you discover it takes 5 seconds to process your 250MB XML file. What are you going to do? How are you going to split this task up so that when you’re 20 function calls deep, your code knows how to stop what it’s doing, and then resume the next time you call it? That is a much harder problem. I knew I could use closures to save a significant amount of state automatically, but I couldn’t figure out a clean way to use them the way I wanted. So I gave up…
Then I stumbled across BrokenFunction’s json code. He had an example of an async json parser. It was recursive, but asynchronous. It could pause itself and resume at semi-arbitrary points. It was brilliant. I took his ideas and expounded on them to write a library I call CoThread. Here is an asynchronous version of the function above:
public function printXMLAsync(xml : XML, indentString : String, context : CoRoutineContext) : void
{
if ((xml == null) || (xml.name() == null))
{
return;
}
else
{
indentString += "\t";
_textWriter.writeLine(indentString + xml.name()+":"+xml.text());
context.foreach(xml.children(),
function printEachChild(child : XML) : Boolean
{
printXMLAsync(child, indentString, context);
return true;
},
this,
function afterDonePrintingChildren() : void
{
_textWriter.writeLine(indentString + "/"+xml.name());
}
);
}
}
Here’s what I like about that code: It’s not extremely different from the synchronous version of the code. I didn’t have to manually save a lot of state. It just works.
In that example I gave the anonymous functions (printEachChild and afterDonePrintingChildren) a name so that they were easier to read, but I don’t have to do that.
Let’s take a look at our pixel example from earlier. Here’s an asynchronous version of that function:
public function processAllPixelsAsync(pixels : Array, context : CoRoutineContext) : void
{
context.foreach(pixels,
function(pixel : int) : Boolean
{
processPixel(pixel);
//Returns true to continue the for loop
return true;
},
this
);
}
Again, this code isn’t terribly different from the original version, and I really like that.
Calling an asynchronous function
All asynchronous functions in the CoThread library take a “CoRoutineContext” as its last parameter. This context is required by the function in order to operate as a CoRoutine (asynchronously), and it has the added benefit of advertising to the world “I am an asynchronous function. I might return immediately, but I probably still have work to do.” To get a CoRoutineContext, you just need a CoThread:
//Instantiate a CoThread for the startPrinting function
var sampleThread : CoThread = new CoThread(startPrinting);
//Start the thread. (passes in its CoRoutineContext)
sampleThread.start();
function startPrinting(context : CoRoutineContext) : void
{
printXMLAsync(sampleXML, "", context);
}
The function printXMLAsync now has a context on which to operate. Furthermore, it can now call any other asynchronous methods it wants, and can simply pass in its own CoRoutineContext. So let’s say that as part of your XML pretty printing, you want to do some Pixel processing.
Here is our original printEachChild function from printXMLAsync:
function printEachChild(child : XML) : Boolean
{
printXMLAsync(child, indentString, context);
return true;
}
Perhaps some of the children of the XML contain a bitmap we want to process:
function printEachChild(child : XML) : Boolean
{
if (child.name() == "pixels")
{
var pixels : Array = getPixelData(child);
//pass our context into the async processing function
//processAllPixelsAsync might take 20 minutes to truly complete, but this
//printEachChild function won't get called again for the next XML node
//until it's done processing
processAllPixelsAsync(pixels, context);
}
else
{
printXMLAsync(child, indentString, context);
}
return true;
}
And processAllPixelsAsync has a CoRoutineContext, so it can in turn call other asynchronous functions quite easily.
Chaining async calls
But when you call an asynchronous function, it can return almost immediately even though it might have 5 minutes of work left to do. Well, the context can come to our rescue again.
Let’s go back to our XML printing example. Recall this function:
function startPrinting(context : CoRoutineContext) : void
{
printXMLAsync(sampleXML, "", context);
}
Imagine we want to do something after we print the XML. You can’t just do this:
function startPrinting(context : CoRoutineContext) : void
{
printXMLAsync(sampleXML, "", context);
//You can't just do this:
trace("I'm done printing the XML!")
}
That’s because the call to printXMLAsync will just start printing the XML. It won’t finish printing all of the XML. So we need to tell our context, “After you’re done with that function, here’s the next function I want you to call.”
We do that with the “pushFunction” call:
function startPrinting(context : CoRoutineContext) : void
{
//Tell the context to call "afterDonePrinting" when it returns from the next asynchronous call
context.pushFunction(afterDonePrinting);
printXMLAsync(sampleXML, "", context);
function afterDonePrinting() : void
{
trace("I'm really done printing the XML!")
}
}
This is relatively readable, but still a bit verbose, and I don’t like that I read the pushFunction call before I read the printXMLAsync call. It doesn’t read in the same order the code is called. Luckily, context.pushFunction returns the context as its return parameter, so we can do this instead:
function startPrinting(context : CoRoutineContext) : void
{
printXMLAsync(sampleXML, "",
context.pushFunction(afterDonePrinting));
function afterDonePrinting() : void
{
trace("I'm really done printing the XML!")
}
}
That’s a bit better. If you want to be even less verbose, you can’t just use an anonymous function:
function startPrinting(context : CoRoutineContext) : void
{
printXMLAsync(sampleXML, "", context.pushFunction(
function() : void
{
trace("I'm really done printing the XML!")
})
);
}
I personally find this the most readable style most of the time. I can write plenty of code inside that anonymous function, including further asynchronous calls, and still manage to read and write it linearly.
Performance
There is a performance penalty for these threaded functions. Instantiating and calling Closures is slower than a normal function call. In my tests using the Debugger, performing these functions takes twice as long as their non-threaded counterparts. I haven’t profiled it extensively to discover the bottlenecks, so it’s quite possible that we can bring it down, but even if we can’t, I think there are still times when you would be willing to pay such a performance penalty. My first use-case for it was saving our budget files without halting the UI of our app. I was happy to have a background save take twice as long as a foreground save. Who cares if it takes an extra few seconds? 5 seconds in the background is MUCH better for the user than 2.5 seconds of an unusable app!
Try CoThread yourself
Although I’m biased, CoThread is my personal favorite “threading” library for ActionScript. It lets me easily write threaded functions without having to wrap my head around a lot of new concepts or put lots of effort into breaking up my code and saving state.
There’s a lot more to the CoThread library, but that probably belongs in the documentation.
You can learn more at the CoThread project page on GitHub, or the mirror on BitBucket.
Note: (Adobe will eventually introduce Worker Threads, and that might well make this code obsolete, but in the meantime I thought I’d post my library because it’s usable today and I think it’s cool.)
