您的位置:首页 > 其它

ActionScript Workers Tutorial (ActionScript多线程教程)

2013-10-17 08:07 399 查看
Originaly From: http://jacksondunstan.com/articles/2401
 

ActionScript workers add threading to AS3 so that you can take advantage of today’s multi-core CPUs. I’ve written a couple of articles about them so far, but skipped over the basics of actually setting them up and using them. This is surprisingly tricky!
Read on for a crash course on how to use workers to speed up your app.

An ActionScript worker is embodied by the
Worker
class. One worker represents one thread. A thread is like the code you’re used to writing, except that it runs at the same time on one of the CPU’s other cores. The code in your main SWF (e.g.
the one you put in an HTML page) is the “main thread” and all the other threads are “worker threads”.

To create a
Worker
, you call the constructor and pass a SWF as a
ByteArray
. This is a really strange way to instantiate a class. You cannot simply pass a function to act as the entry point of your new thread. This leads to two distinct ways of organizing your code for workers.

The first way is what I call the “all-in-one” approach. In this approach the bytes of your normal, main thread SWF are used to create the
Worker
. Since the same code can be either the main thread or a worker thread, an
if
statement
is used to detect which of these modes the code is running as. Thankfully, the worker API makes this easy. Here’s an example:

public class MainThread extends Sprite
{
public function MainThread()
{
if (Worker.current.isPrimordial)
{
startMainThread();
}
else
{
startWorkerThread();
}
}

private function startMainThread(): void
{
// Use our own SWF bytes to create the worker thread
var worker:Worker = WorkerDomain.current.createWorker(loaderInfo.bytes);

// ... more main thread setup
}

private function startWorkerThread(): void
{
// ... worker thread setup
}
}


 

This approach allows you to keep all of your code in one codebase, just like you’re used to with single-threaded code. Unfortunately, you’ve just mingled the main thread code with the worker thread code. So you’ll probably want to use classes to at least
keep the code in different files.

The other approach is to have to main classes: one for the main thread and one for the worker thread. You compile the worker thread’s main class, compile the main thread’s main class with the worker thread embedded, then use those embedded bytes to create
the worker thread. Here’s how that looks:

////////////////
// MainThread.as
////////////////
public class MainThread extends Sprite
{
[Embed(source="WorkerThread.swf", mimeType="application/octet-stream")]
private static var WORKER_SWF:Class;

public function MainThread()
{
var workerBytes:ByteArray = new WORKER_SWF() as ByteArray;
var worker:Worker = WorkerDomain.current.createWorker(workerBytes);

// ... more main thread setup
}
}
//////////////////
// WorkerThread.as
//////////////////
public class WorkerThread extends Sprite
{
public function WorkerThread()
{
// ... worker thread setup
}
}


 

Both approaches have upsides and downsides. You may choose your approach based on whichever organization structure makes the most sense for you and your project. The only real technical difference is that the “two SWF” method will have your other AS3 code
compiled into both SWFs, thus increasing the total SWF size. However, that’s often not much of a concern these days as pure-code SWFs tend to be quite small.

The next step is to create
MessageChannel
objects so that your threads can communicate with each other. This isn’t strictly necessary, but almost all multi-threaded code will need some way for the threads to collaborate. The
MessageChannel

class represents an asynchronous communication between the threads, similar to socket communication. Most objects sent over the
MessageChannel
will be copied as the threads do not usually have direct access to each others’ objects.

MessageChannel
on its own isn’t very useful. A
MessageChannel
object needs to be placed into a “shared property” that both threads can access. Think of it like a drop box: one thread places a named
MessageChannel
and
the other retrieves it by that name. The two threads now use this
MessageChannel
to send and receives messages between them.

The main thread and the worker threads set up their
MessageChannel
objects in different ways. Here’s how it looks:

// Set up a MessageChannel to send messages from the main thread to a worker thread
// Since this is called from the main thread, Worker.current is the main thread
var mainToWorker:MessageChannel = Worker.current.createMessageChannel(worker);
worker.setSharedProperty("mainToWorker", mainToWorker);

// Set up a MessageChannel to receive messages from a worker thread into the main thread
var workerToMain:MessageChannel = worker.createMessageChannel(Worker.current);
workerToMain.addEventListener(Event.CHANNEL_MESSAGE, onWorkerToMain);
worker.setSharedProperty("workerToMain", workerToMain);
function onWorkerToMain(ev:Event): void
{
}


 

Because the threads are executing at the same time, you need to pay close attention to when the message channels are set as shared properties and when they are retrieved. You don’t want one thread to overwrite another thread’s
MessageChannel

and you don’t want to retrieve a
MessageChannel
that hasn’t been set.

To avoid this setup problem, I recommend a simple strategy: the main thread creates all of the
MessageChannel
objects and sets them as shared properties before the worker thread is started. Here’s how that process goes:

////////////////
// MainThread.as
////////////////
public class MainThread extends Sprite
{
[Embed(source="WorkerThread.swf", mimeType="application/octet-stream")]
private static var WORKER_SWF:Class;

var mainToWorker:MessageChannel;
var workerToMain:MessageChannel;

public function MainThread()
{
var workerBytes:ByteArray = new WORKER_SWF() as ByteArray;
var worker:Worker = WorkerDomain.current.createWorker(workerBytes);

// Send to worker
mainToWorker = Worker.current.createMessageChannel(worker);
worker.setSharedProperty("mainToWorker", mainToWorker);

// Receive from worker
workerToMain = worker.createMessageChannel(Worker.current);
workerToMain.addEventListener(Event.CHANNEL_MESSAGE, onWorkerToMain);
worker.setSharedProperty("workerToMain", workerToMain);
}

private function onWorkerToMain(ev:Event): void
{
}
}
//////////////////
// WorkerThread.as
//////////////////
public class WorkerThread extends Sprite
{
var mainToWorker:MessageChannel;
var workerToMain:MessageChannel;

public function WorkerThread()
{
// Receive from main
// Since this is called from the worker thread, Worker.current is the worker thread
mainToWorker = Worker.current.getSharedProperty("mainToWorker");
mainToWorker.addEventListener(Event.CHANNEL_MESSAGE, onMainToWorker);

// Send to main
workerToMain = Worker.current.getSharedProperty("workerToMain");
}

private function onMainToWorker(event:Event): void
{
}
}

After the main thread has created and set its
MessageChannel
objects, it’s time to start the worker thread. That’s as easy as calling
thread.start()
, but how do you know when the worker thread has finished its work starting up? If
the main thread immediately starts sending it messages the worker thread may not have even set up its event listeners on the
MessageChannel
objects. In this case the messages wouldn’t be received and potentially important tasks for the worker thread
may be missed.

To avoid this situation I recommend a “startup”
MessageChannel
. This
MessageChannel
is, like all the other
MessageChannel
objects, created by the main thread for one purpose: to receive a message from the worker thread indicating that it’s finished starting up and is ready to be used. The following code shows how that works, including your first glimpse
at actually sending and receiving messages using a
MessageChannel
:

////////////////
// MainThread.as
////////////////
public class MainThread extends Sprite
{
[Embed(source="WorkerThread.swf", mimeType="application/octet-stream")]
private static var WORKER_SWF:Class;

var mainToWorker:MessageChannel;
var workerToMain:MessageChannel;
var workerToMainStartup:MessageChannel;

public function MainThread()
{
var workerBytes:ByteArray = new WORKER_SWF() as ByteArray;
var worker:Worker = WorkerDomain.current.createWorker(workerBytes);

// Send to worker
mainToWorker = Worker.current.createMessageChannel(worker);
worker.setSharedProperty("mainToWorker", mainToWorker);

// Receive from worker
workerToMain = worker.createMessageChannel(Worker.current);
workerToMain.addEventListener(Event.CHANNEL_MESSAGE, onWorkerToMain);
worker.setSharedProperty("workerToMain", workerToMain);

// Receive startup message from worker
workerToMainStartup = worker.createMessageChannel(Worker.current);
workerToMainStartup.addEventListener(Event.CHANNEL_MESSAGE, onWorkerToMain);
worker.setSharedProperty("workerToMainStartup", workerToMainStartup);

worker.start();
}

private function onWorkerToMain(ev:Event): void
{
}

private function onWorkerToMainStartup(ev:Event): void
{
var success:Boolean = workerToMainStartup.receive() as Boolean;
if (!success)
{
// ... handle worker startup failure case
}
}
}
//////////////////
// WorkerThread.as
//////////////////
public class WorkerThread extends Sprite
{
var mainToWorker:MessageChannel;
var workerToMain:MessageChannel;
var workerToMainStartup:MessageChannel;

public function WorkerThread()
{
// Receive from main
mainToWorker = Worker.current.getSharedProperty("mainToWorker");
mainToWorker.addEventListener(Event.CHANNEL_MESSAGE, onMainToWorker);

// Send to main
workerToMain = Worker.current.getSharedProperty("workerToMain");

// Send startup message to main
workerToMainStartup = Worker.current.getSharedProperty("workerToMainStartup");
workerToMainStartup.send(true);
}

private function onMainToWorker(event:Event): void
{
}
}

You may be tempted to use the built-in
WorkerState
events to synchronize your thread startup. It’s very appealing to avoid the extra steps you see above by simply listening for the
WorkerState.RUNNING
event like so:

// In the main thread...
worker = new Worker(workerBytes);
worker.addEventListener(Event.WORKER_STATE, onWorkerState);
function onWorkerState(ev:Event): void
{
if (worker.state == WorkerState.RUNNING)
{
worker.start();
// ... start using the worker
}
}


 

Do not be fooled! The
RUNNING
state only means that the
Worker
has been created and begun executing its constructor. It may or may not have finished its constructor, so you will most likely get intermittent problems due to the worker
thread not having finished setting itself up. A “startup”
MessageChannel
avoids this possibility and ensures a smooth thread startup.

Now that you’ve safely created your worker thread established communication via a
MessageChannel
, you’re ready to start actually using the thread. This part is totally up to your application, but in general you want to hand the worker thread
big tasks via
MessageChannel.send()
and then receive the results via
MessageChannel.receive()
. The possibilities are endless here, so let your imagination run wild. Just remember, if you don’t use any worker threads you are potentially letting half (dual core), three-quarters (quad core), or even seven-eighths (octo-core)
of the CPU sit idle.

 

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息