Intro to AS3 Workers (Part 1): Hello World

[UPDATE]: Part 2 and Part 3 of this tutorial are available now

[UPDATE 2]: Adobe has removed support for ByteArray.shareable until Player 11.5.

With the beta release of AIR 3.4 and Flash 11.4, Adobe has introduced one of the most requested API’s for years: Multi-threading!

With AS3 workers it’s now very easy to create true multi-threaded applications with just a few lines of code. The API is fairly straightforward, and they’ve done some very handy things like a new ByteArray.shareable property to share memory between worker’s, and a new BitmapData.copyPixelsToByteArray API for quickly converting bitmapData to ByteArray.

In this post I’ll go through the various components of the Worker API, and we’ll look at a simple little HelloWorker application.

If you would like to follow along, you can download the FlashBuilder Project Files:

If you just want to see the code, without any crap:

So what is a worker?

Put simply a worker is just another SWF that’s running alongside your main SWF. For a an in depth background check out Thibault Imbert’s excellent writeup.

To create a worker you call WorkerDomain.current.createWorker()  function and pass it the bytes of a SWF.

There’s currently 3 ways to generate these SWF bytes:

  1. Use the loaderInfo.bytes of the main SWF, and check the Worker.current.isPrimordial property inside your document class’s constructor. This is the quickest way to create a worker, and has the advantage of instant testing-debug cycle.
     
  2. Publish a SWF file, and [Embed] it in your main project. This will have tooling support in FlashBuilder 4.7, but until then it’s quite cumbersome. Everytime you change the code in your worker, you must re-export the SWF, this gets annoying pretty quick!
     
  3.  Use Worker From Class a new Library which allows you to create workers directly from Classes. This looks like the most optimal solution right now, but does  introduce a couple of external dependencies in your project.
     

In this initial tutorial I’m going to focus on the first method, using our own loaderInfo.bytes. This is the quick and dirty way to do it. In a follow up tutorial I’ll take a look at Worker From Class.

For a great tutorial on method #2 check out Lee Brimelow’s video’s part 1 and part 2.

Lets talk.

In any multi-threaded scenario communication is key. Transferring memory from one thread to another is expensive, and sharing memory takes careful planning and consideration. Much of the challenge in implementing a worker system comes from finding the right architecture for sharing data to and from your workers.

To help us out Adobe has given us a few simple (but flexible) ways to send messages.

worker.setSharedProperty() / worker.getSharedProperty()

This the simplest but most limited way to pass values around. You can call worker.setSharedProperty(“key”, val) to set things, and WorkerDomain.current.getSharedProperty(“key”) to get them on the other side. For example:

0
1
2
3
4
5
 
//In main thread
worker.setSharedProperty("foo", true);
 
//In worker thread
var foo:Boolean = Worker.current.getSharedProperty("foo");

You can store both simple and complex objects here, but for most cases the data is serialized, it’s not actually shared. If a value changes on one end, it will not be updated on the other until you call share/get again.

The exception is if you pass a ByteArray with byteArray.shareable=true, or a MessageChannel. Coincidentally, these are the other two methods of communication :)

MessageChannels

MessageChannels are like one way conduits from one worker to another. They use a combination of Events and a simple Queue system. You will call channel.send() on one end, on the other end an Event.CHANNEL_MESSAGE will be dispatched. In the handler for the event, you will call channel.receive() to receive what was sent. As a I mentioned, this functions like a queue. So, you can send() and receive() multiple times on each end. For example:

0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 
//In main thread
mainToWorker.send("ADD");
mainToWorker.send(2);
mainToWorker.send(2);
 
//In worker thread
function onChannelMessage(event:Event):void {
 
    var msg:String = mainToWorker.receive();
    if(msg == "ADD"){
        var val1:int = workerToMain.receive();
        var val2:int = workerToMain.receive();
        var result:int = val1 + val2;
         //Send back the result to main thread
        workerToMain(result);
    }
 
}

MessageChannels are shared using worker.setSharedProperty(), and can be shared throughout as many workers in your Application as you would like, but they only ever have one destination. As such, it seems to be a good convention to name them by their receiver, ie channelToWorker or channelToMain

Example:

0
1
2
3
4
5
6
7
8
9
10
11
 
//In main thread
workerToMain = worker.createMessageChannel(Worker.current);
worker.setSharedProperty("workerToMain", workerToMain);
 
mainToWorker = Worker.current.createMessageChannel(worker);
worker.setSharedProperty("mainToWorker", mainToWorker);
 
 
//In worker thread
workerToMain = Worker.current.getSharedPropert("workerToMain");
mainToWorker= Worker.current.getSharedPropert("mainToWorker");

An important limitation to both MessageChannel and sharedProperties, is that the data is serialized when it is sent. That means it needs to be deconstructed, transferred, and reconstructed on the other side. This can be costly. Due to this limitation, these API’s are best used for transfering small amounts of data intermittently.

So what if you do need to share a huge chunk of data? The answer is a shareable ByteArray…

byteArray.shareable

The fastest way to transfer data is to not transfer it at all!

Thankfully Adobe has given us the ability to share a ByteArray directly. Since we can store virtually anything inside a ByteArray, this is extremely powerful. To share a byteArray, you set  byteArray.shareable=true and then use messageChannel.send(byteArray) or worker.setSharedPropert(“byteArray”, byteArray) to share it.

Once your byteArray is shared, you can write values directly into it, and read from it on the other end instantly. Beautiful.

That is basically all there is to it. As you can see, there are 3 distinct ways to share data, and you could combine them in many many different ways. Now that we’ve gone over the structure, lets look at a simple Hello World.

Sample Application

First, in order to get your projects compiling, you’ll need to do a couple things:

  • Download the latest AIR 3.4 SDK, or playerglobal.swc file, make sure your project is using them.
  • Add compile flag “swf-version=17″ to your project
  • Install FlashPlayer 11.4 standalone debugger

With that, you should now see the various Worker related API’s such as Worker, WorkerDoman, MessageChannel etc. If you’re having trouble with this, check out the first couple minutes of Lee Brimelow’s video where he walks through setup.

You can also just download a copy o my FlashBuilder Project which should just work.

Step 1 – The Document Class

First we’ll need to create our document class:

0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public class HelloWorldWorker extends Sprite{
 
protected var mainToWorker:MessageChannel;
protected var workerToMain:MessageChannel;
protected var worker:Worker;
 
public function HelloWorldWorker()
{
	/** 
	 * Start Main thread
	 **/
	if(Worker.current.isPrimordial){
		//Create worker from our own loaderInfo.bytes
		worker = WorkerDomain.current.createWorker(this.loaderInfo.bytes);
 
		//Create messaging channels for 2-way messaging
		mainToWorker = Worker.current.createMessageChannel(worker);
		workerToMain = worker.createMessageChannel(Worker.current);
 
		//Inject messaging channels as a shared property
		worker.setSharedProperty("mainToWorker", mainToWorker);
		worker.setSharedProperty("workerToMain", workerToMain);
 
		//Listen to the response from Worker
		workerToMain.addEventListener(Event.CHANNEL_MESSAGE, onWorkerToMain);
 
		//Start worker (re-run document class)
		worker.start();
 
		//Set an interval that will ask the worker thread to do some math
		setInterval(function(){
			mainToWorker.send("HELLO");
			trace("[Main] HELLO");
		}, 1000);
 
	} 
	/** 
	 * Start Worker thread 
	 **/
	else {
		//Inside of our worker, we can use static methods to 
		//access the shared messgaeChannel's
		mainToWorker = Worker.current.getSharedProperty("mainToWorker");
		workerToMain = Worker.current.getSharedProperty("workerToMain");
 
		//Listen for messages from Main
		mainToWorker.addEventListener(Event.CHANNEL_MESSAGE, onMainToWorker);
	}
}
}

Walking through this step by step:

  • We use the Worker.current.isPrimordial to determine whether we’re the main thread or the worker.
  • In the main thread, we create our message channels and share them with the worker.
  • We start the worker, which causes the document class to be instanciated again
  • The worker is created, and fetches a reference to the shared MessageChannels

I’ve also included a small interval which will send a “HELLO” message to the worker every 1000ms. Next we’ll make the The worker return “WORLD” and trace it out in the main thread.

Step 2: Handling Events

Now that we have shared message channels, we can easily talk between our workers. You can see that we already setup the reference to the eventListener’s in the code above, so all that’s left to do is create them:

0
1
2
3
4
5
6
7
8
9
10
11
12
13
//Receive messages FROM the Main Thread
protected function onMainToWorker(event:Event):void {
	var msg:* = mainToWorker.receive();
	//When the main thread sends us HELLO, we'll send it back WORLD
	if(msg == "HELLO"){
		workerToMain.send("WORLD");
	}
}
 
//Receive messages FROM the Worker
protected function onWorkerToMain(event:Event):void {
	//Trace out whatever message the worker has sent us.
	trace("[Worker] " + workerToMain.receive());
}

If you run this application now, you will see “HELLO” and “WORLD” traced out every 1000ms. Congrats on your first multi-threaded application!

Step 3: A little more?

Ok, so admittedly, that’s a little useless. Lets enhance our demo and bit, and make it do some math. How abut 2+2?

First, lets modify the interval function to look like this:

0
1
2
3
4
5
6
//Set an interval that will ask the worker thread to do some math
setInterval(function(){
	mainToWorker.send("ADD");
	mainToWorker.send(2);
	mainToWorker.send(2);
        trace("[Main] ADD 2 + 2?");
}, 1000);

The we’ll modify our listener in the worker, to look for these values:

0
1
2
3
4
5
6
7
8
9
protected function onMainToWorker(event:Event):void {
	var msg:* = mainToWorker.receive();
	if(msg == "ADD"){
		//Receive the 2 numbers and add them together
                var val1:int = mainToWorker.receive();
                var val2:int = mainToWorker.receive();
		//Return the result to the main thread
		workerToMain.send(val1 + val2);
	}
}

That’s it! If you run the application now, you’ll see “[Main] ADD 2 + 2?” and the correct answer of “4″ traced out.

If you would like to view the completed class:

In the next tutorial we’ll step it up a notch and look at how we can do some image processing.

Written by

22 Comments to “Intro to AS3 Workers (Part 1): Hello World”

  1. [...] Intro to AS3 Workers (Part 1): Hello World Intro to AS3 Workers (Part 2): Image Processing [...]

  2. Kahlil says:

    Very nice. It’s almost too simple. Yet powerful with the byteArray. Seems as if mastering how to use a byteArray is the key to exploiting this functionality.

    • shawn says:

      Yep! Actually if you’re not updating data every frame, just using byteArray.writeObject(myVector); is pretty quick and dead easy to use.

      eg:
      MainThread:
      byteArray.writeObject(myVector);

      Worker:
      myVector = byteArray.readObject();

  3. [...] AS3多线程快速入门(一):Hello World[译] 添加评论[作者:Dom Chen 分类:ActionScript, Flash ] 原文链接:http://esdot.ca/site/2012/intro-to-as3-workers-hello-world [...]

  4. Darwin says:

    Aloha.

    When I try to run your project, Flash Builder produces the following error message: VerifyError: Error #1014: Class flash.system::MessageChannel could not be found.

    When I try to run it from my browser with Flash Player 11.4.400.252 installed, nothing shows up.

    I must have something installed wrong. Is the FlashPlayer 11.4 standalone debugger required? If so, do you have a link to it?

    Have a great day.

    Take care,
    Darwin

    • shawn says:

      Ya you need to make sure you’re usng the 11.4 Standalone debugger, and also make sure to remove the default playerglobal.swc and include the one in my libs folder.

  5. [...] 在《AS3多线程快速入门》系列教程的第一部分中,我们研究了AS3 Worker的基本原理,包括多种通信方式,还展示了一个简单例子:Hello World Worker。 [...]

  6. [...] 在《AS3多线程快速入门》系列教程的第一部分中,我们研究了AS3 Worker的基本原理,包括多种通信方式,还展示了一个简单例子:Hello World Worker。 [...]

    • Thanks for this post, especially “Download the latest AIR 3.4 SDK, or playerglobal.swc file, make sure your project is using them.
      Add compile flag “swf-version=17″ to your project” – very useful for the terminally impatient like myself :)

      simon

  7. tdadone says:

    Im having a problem with the worker auto-unloading.

    The scenario is the following:

    - I have a background worker loaded using the Embed method (also tried with a loader).
    - I load another SWF which has no relation to the worker, let’s call it ‘random.swf’
    - I unloadAndStop the ‘random.swf’
    - I try to send a message to the background worker.
    - The debugger tells me the MessageChannel is closed and the background worker has been unloaded.

    Here is an example project https://dl.dropbox.com/u/39362310/MP3Worker.zip

    • rewb0rn says:

      Hello,

      we found it is not working using FDT on Mac in Debug Mode, instead we had to compile in Release Mode and use SOS max for tracing. Maybe this will save someone’s time.

      Cheers,
      Ruben

      • pigiuz says:

        thank you ruben,
        this saved my time.
        do you (or anyone else) know if this is solved?

        i found it not working even in flash builder 4.6 on osx debug mode

  8. Joro says:

    Thanks for the nice tutorial!
    You point out that using shareable byte array is supposedly faster than AMF-based (de)serialization. However, it would seem that bytearray’s readObject & writeObject also do AMF-based serialization. So what is to gain there? Also, how does this work with non-trivial objects (e.g. descendants of DisplayObject)? Can workers pass around DisplayObjects?

    • shawn says:

      writeObject is slow, but writeInt() isn’t.

      Say I need the position data of a sprite, tnstead of passing a complex object, I can just writeInt(display.x), writeInt(display.y) and then do the opposite on the other end. Always in the exact same order of course :)

  9. Tufik says:

    For me, the WorkerFactory.as only work in Debug configuration.
    When i run the code in the browser or Flash professional it no work and the worker return is NULL.

    I think that is a problem of the as3swf, because this can’t find the classTag

    • bronstein says:

      I’m having this same problem, tried changing sdk’s, playerglobal.swc, switched between 11.4,11.6 and 11.7, the most I can get going is 11.4 debug mode, when compiling for release worker is null.

  10. Joe says:

    The Flash Project link is 404 – it’s never clear from any Worker example I’ve found how the inherited Sprite class is used at the Application level. Good writeup though.

  11. david says:

    Hi Shawn,

    i’m having trouble, getting your examples to work, or any Worker-example out there.

    I get no compiler-errors, not runtime-erros, and i tried compiling for 11.4 and 11.5. The Worker just never responds, and the main-thread does not receive any sends, although i get a WORKER_STATE event, that says the worker is running.

    (The 11.6 player cannot find the MessageChannel-class although it’s included in the 11.6 playerglobal)

    The only difference to your setup is, that im working with flashdevelop and not flashbuilder, but that can’t be the problem.

    Can you confirm, that your examples still work in the current player?

    • shawn says:

      Hey David, the examples still work good for me, but you have to make sure to disable Chrome’s PPAPI plugin and use the Adobe one instead.

      :/

      And yes, if you’re wondering, this basically means that Workers is completely fragmented and useless until Google fixes it. PPAPI sucks, it’s not the only major regression it’s introduced.

    • Michiel Brinkers says:

      I’m having the same issue with FDT. Everything compiles and the main thread does send out messages, but I never get anything back from the worker.

      • Michiel Brinkers says:

        Turns out it’s the older versions of FDT (an probably Flash Develop as well) that don’t work in with workers in debug mode. If you run instead of debug the application, it will work.

  12. KP says:

    I have an existing Air application that needs to poll a rest service for information every so often. However the poll is causing UI freeze. I read about Workers and would like to use them.

    I have FlashBuilder 4.6, Apache’s 4.9.1 Flex SDK and have installed the Flash Player 11 debugger plugin with Firefox, though as this is an air app I’m trying to work with I don’t know if that’s the right thing.

    I’m trying to use the example project zip file you provided to Joe. I get the VerifyError message channel cannot be found. I saw your resolution of this to Darwin but don’t know how to do what you are suggesting and can’t find a clear example elsewhere.

    Please can you make a completely idiot proof 1. go here, 2. do this, 3. do that?

  13. KP says:

    Further to my previous message, I got the example zip to run. Main seems to fire the 2+2 requests but the worker never responds.

  14. Mark says:

    Is it possible to build a complex display view using A Worker and pass it to Main process to show it to the user?

    Thanks

  15. sameer hwety says:

    did worker support in Flash Builder 4.6 since i tried to add it but it doesn’t work ?
    If supported ,firstly , how to implement it in Flash Builder and using it . secondly , did it support for flex project or Action Script project or both ?

Leave a Reply to Joro

Message