ActionScript Workers: Condition Class Demo
The last article gave a very basic example of the flash.concurrent.Condition
class introduced in Flash Player 11.5. That example was (hopefully) a simple and easy way to understand the mechanics of how the Condition
class works. Unfortunately, it was not a useful example and actually demonstrated the opposite of what you’d want to use it for. Today’s article shows a somewhat more complicated example that should serve as an example of appropriate usage for Condition
.
If you’ve never used Condition
before or aren’t used to its equivalents in other languages, it can be somewhat difficult to understand at first. On top of that, the official documentation is rather sparse and includes no code examples. This article and its predecessor hope to help.
The first thing to remember is that Condition
wraps up a Mutex
and provides some convenience functionality. In order to call notify()
, notifyAll()
, or wait()
on it, the worker you’re calling from needs to have locked that Mutex
. This often means that you’ll call lock()
on the Mutex
before one of these operations. For more on the mechanics, see the previous article.
The following demo uses a worker thread to compress a big ByteArray
. This takes a long time and ByteArray.compress()
blocks until the whole compression is complete. Since we don’t want to block the main worker thread from updating the UI, taking in user input, and so forth, we’ll use another worker thread to do the compression while the main worker thread continues to update.
For simplicity’s sake, this demo simply shows a button that you can use to start the compression. A simple TextField
updates every frame while the compression takes place to let you know that the UI is still updating. If you click the button to start the compression while the compression is already occurring, an error will be displayed. This lets you know that the app can still take in user input. When compression is complete, a simple error message with some stats is displayed and you can perform more compressions.
package { import flash.concurrent.Mutex; import flash.concurrent.Condition; import flash.display.Sprite; import flash.events.Event; import flash.events.MouseEvent; import flash.text.TextField; import flash.text.TextFieldAutoSize; import flash.text.TextFormat; import flash.system.MessageChannel; import flash.system.Worker; import flash.system.WorkerDomain; import flash.utils.ByteArray; import flash.utils.getTimer; /** * Demo of uses for the Condition class. Uses a worker thread to compress * a big ByteArray without blocking the main (UI) worker thread. * @author Jackson Dunstan (JacksonDunstan.com) */ public class ConditionDemo extends Sprite { /** 128 megabytes */ private static const SIZE:uint = 1024*1024*128; /** Logger for the status of the compression */ private var logger:TextField = new TextField(); /** Error message display */ private var errorMessage:TextField = new TextField(); /** Worker that compresses for the main worker */ private var worker:Worker; /** Bytes to compress */ private var bytes:ByteArray; /** Condition the worker is waiting on to start the compression */ private var condition:Condition; /** Message channel from the worker thread to the main thread */ private var workerToMain:MessageChannel; /** * Start the app in main thread or worker thread mode */ public function ConditionDemo() { // If this is the main SWF, start the main thread if (Worker.current.isPrimordial) { startMainThread(); } // If this is the worker thread SWF, start the worker thread else { startWorkerThread(); } } /** * Start the main thread */ private function startMainThread(): void { // Create the worker from our own SWF bytes var swfBytes:ByteArray = this.loaderInfo.bytes; worker = WorkerDomain.current.createWorker(swfBytes); // Create a message channel to receive from the worker thread workerToMain = worker.createMessageChannel(Worker.current); workerToMain.addEventListener(Event.CHANNEL_MESSAGE, onWorkerToMain); worker.setSharedProperty("workerToMain", workerToMain); // Start the worker worker.start(); // Make a button to start the compressing makeButton("Compress", onCompressButtonClicked); // Show a logger logger.y = height + 10; logger.autoSize = TextFieldAutoSize.LEFT; logger.defaultTextFormat = new TextFormat("_sans", 11); addChild(logger); // Show an error message errorMessage.x = width + 10; errorMessage.autoSize = TextFieldAutoSize.LEFT; errorMessage.defaultTextFormat = new TextFormat("_sans", 11, 0xff0000); addChild(errorMessage); } /** * Start the worker thread */ private function startWorkerThread(): void { // Create a condition indicating that we're waiting for bytes to // compress condition = new Condition(new Mutex()); Worker.current.setSharedProperty("condition", condition); // Get the message channel for notifying the main worker thread that // we're done compressing workerToMain = Worker.current.getSharedProperty("workerToMain"); // Endlessly loop compressing bytes that the main worker thread // gives us while (true) { // Lock the condition mutex so the worker thread can wait on // the condition. This pauses the worker thread until the mutex // can be locked. condition.mutex.lock(); // Wait for the bytes to be ready for compression. This releases // the condition's mutex and pauses the worker thread until the // main worker thread calls notify() on the condition. condition.wait(); // Get the bytes to compress bytes = Worker.current.getSharedProperty("bytes"); // Compress the bytes bytes.compress(); // Notify the main thread that the bytes have been compressed workerToMain.send(true); } } private function onCompressButtonClicked(ev:Event): void { // Get the condition the worker created for us condition = worker.getSharedProperty("condition"); // Try to take ownership of the condition mutex if (condition.mutex.tryLock()) { // Create a big ByteArray for the worker to compress bytes = new ByteArray(); bytes.length = SIZE; bytes.shareable = true; worker.setSharedProperty("bytes", bytes); // Clear the status text displays errorMessage.text = ""; logger.text = ""; // The worker is waiting, so we got the lock. Notify the worker // that the bytes are ready and release our ownership of the // mutex since we only needed it to notify. condition.notify(); condition.mutex.unlock(); // Check if the worker is done every frame. This demonstrates // that the main worker thread is not blocked while the worker // thread compresses the bytes. addEventListener(Event.ENTER_FRAME, onEnterFrame); } else { // The worker is not waiting, so show an error message // indicating that it's too busy for a new request errorMessage.text = "Error: worker busy at " + getTimer(); } } private function onEnterFrame(ev:Event): void { // We did not get the mutex. This indicates that the compression // is not yet complete. Show a message indicating that. logger.text = "Compressing. Time: " + getTimer(); } /** * Callback for when the worker thread sends a message to the main thread * via a MessageChannel * @param ev CHANNEL_MESSAGE event */ private function onWorkerToMain(ev:Event): void { // The message indicates that the compression is complete, so stop // checking every frame. removeEventListener(Event.ENTER_FRAME, onEnterFrame); // Output some statistics about the work that was done logger.text= "Done compressing " + SIZE + " bytes to " + bytes.length + " bytes (" + ((bytes.length*100.0)/SIZE).toFixed(2) + "%)"; } private function makeButton(label:String, callback:Function): void { const PAD:Number = 3; const TEXT_FORMAT:TextFormat = new TextFormat("_sans", 11); var tf:TextField = new TextField(); tf.defaultTextFormat = TEXT_FORMAT; tf.name = "label"; tf.text = label; tf.autoSize = TextFieldAutoSize.LEFT; tf.selectable = false; tf.x = tf.y = PAD; var button:Sprite = new Sprite(); button.name = label; button.graphics.beginFill(0xE6E2D1); button.graphics.drawRect(0, 0, tf.width+PAD*2, tf.height+PAD*2); button.graphics.endFill(); button.graphics.lineStyle(1, 0x000000); button.graphics.drawRect(0, 0, tf.width+PAD*2, tf.height+PAD*2); button.addChild(tf); button.addEventListener(MouseEvent.CLICK, callback); button.x = PAD + this.width; button.y = PAD; addChild(button); } } }
Spot a bug? Have a question or suggestion? Post a comment!
#1 by tim on December 26th, 2013 ·
#2 by jackson on December 26th, 2013 ·
That’s an interesting crash. The main worker thread is trying to use the
Condition
it gets from the worker thread, but it’s null. TheCondition
is created immediately by the worker thread and set as a shared property, so it should definitely be ready by the time the next frame with the “compress” button is shown and ready for the click event on the following frame. So I’m not sure how this is occurring for you. What’s your environment (e.g. OS, player version, browser)? Are you using my SWF or did you compile on your own? Do you have reproduction steps?#3 by Jason on December 27th, 2013 ·
VerifyError: Error #1014: can not find Class flash.system::Worker。
#4 by Jason on December 27th, 2013 ·
My develop tool is flashBuilder4.6, SDK is 4.6.2. And I can get the Work Class from code prompt, but it’s wrong. I am a rookie, if you can help me, I will very thanks for you. en, my English is not good,hei hei.~~~~(>_<)~~~~
#5 by jackson on December 27th, 2013 ·
That’s the kind of error you’d get if your Flash Player doesn’t have the
Worker
class for your SWF version. Try double-checking that you’ve set the SWF version properly and that the Flash Player you’re trying to run the SWF in is at least version 11.4 (forWorker
) or 11.5 (forCondition
).#6 by Jason on December 27th, 2013 ·
Can I run these code in my Flash CS5.5 ?
#7 by jackson on December 29th, 2013 ·
I’m not sure about Flash Pro, but any Flash Player browser plugin from 11.5 and up should be able to run it.
#8 by Jason on January 1st, 2014 ·
Thanks for your answer ,thank youO(∩_∩)O~
#9 by mikeciz on January 2nd, 2014 ·
Hi Jackson. Thanks for all the great info! Maybe you can help me with a problem I’m having. I’m trying to run a worker on Android and having some issues. The worker class is a modified version of the one found here: https://github.com/silviopaganini/AS3-Workers-Manager
In FB 4.7 Simulator on Mac/PC = WORKS GREAT.
On our Android tablets in Debug Mode = WORKS GREAT.
Release Build on tablet = FAIL. It fails to create the worker and Worker.supported = false. I’ve got -swf-version=22 and AIR SDK 3.9. (app.xml has 3.9 listed). We are using Starling/Feather direct rendermode etc.
I must be overlooking something but I can’t put my finger on it.
Any help is greatly appreciated! Keep up the good work. Love the site!
#10 by jackson on January 4th, 2014 ·
I’m not sure what the issue could be. It seems like an environment setup issue though since workers are supported on AIR for Android. I’d recommend starting with a test project that simply puts the value of
Worker.isSupported
on aTextField
. That way you can make sure that none of the rest of your project setup is getting in the way. Once you’ve got that working you can try to figure out the difference between the simple, working project and the real one. Best of luck!#11 by Marcos Antônio on September 20th, 2015 ·
This error on flashbuilder 4.7 swf-version=30
ArgumentError: Error #3735: This API cannot accept shared ByteArrays.
at flash.utils::ByteArray/_compress()
at flash.utils::ByteArray/compress()
at ConditionDemo/startWorkerThread()[C:\Users\usuario\Adobe Flash Builder 4.7\ConditionDemo\src\ConditionDemo.as:127]
at ConditionDemo()[C:\Users\usuario\Adobe Flash Builder 4.7\ConditionDemo\src\ConditionDemo.as:58]
#12 by zaza on August 7th, 2018 ·
since Flash version 30.x.x shareable bytearray is disable to mitigate the global CPU vulnerabilities spectre and meltdown (which is not Flash vulnerabilities)
https://www.adobe.com/go/fp-spectre
It would be nice to see this example with Messages rather than shareable byterarray
thanks!