ActionScript workers allow you to take advantage of today’s multi-core processors by creating multiple threads of execution. These threads will invariably need to share some data between them. By default, all data passed between the workers/threads is copied, which can be really slow. The ByteArray class can be shared without copying. Today’s article discusses this and talks about some quirks that come along with it.

Sharing a ByteArray is really easy. The first step is to mark it as shareable:

bytes.shareable = true;

Then you set it as a shared property on a worker:

worker.setSharedProperty("bytes", bytes);

The worker then retrieves the shared bytes:

var bytes:ByteArray = Worker.current.getSharedProperty("bytes");

All of this is done without any copying of the ByteArray, which could be disastrously slow if the ByteArray is quite long. So now that two workers can read and write the same bytes, can they collaborate with the length and position fields, too? That is, do all workers/threads with the ByteArray see the same length and position fields?

The following simple test app answers the question in a mere 70 lines:

package
{
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.utils.ByteArray;
	import flash.text.TextField;
	import flash.text.TextFieldAutoSize;
	import flash.system.Worker;
	import flash.system.WorkerDomain;
	import flash.system.MessageChannel;
 
	public class ByteArrayTest extends Sprite
	{
		private var logger:TextField = new TextField();
 
		private function row(...cols): void
		{
			logger.appendText(cols.join(",")+"\n");
		}
 
		public var worker:Worker;
		public var workerToMain:MessageChannel;
 
		public function ByteArrayTest()
		{
			logger.autoSize = TextFieldAutoSize.LEFT;
			addChild(logger);
 
			if (Worker.current.isPrimordial)
			{
				startMainThread();
			}
			else
			{
				startWorkerThread();
			}
		}
 
		private function startMainThread(): void
		{
			var bytes:ByteArray = new ByteArray();
			bytes.length = 20;
			bytes.position = 10;
			bytes.shareable = true;
 
			row("Reporter", "Length", "Position");
			row("Main", bytes.length, bytes.position);
 
			worker = WorkerDomain.current.createWorker(this.loaderInfo.bytes);
			workerToMain = worker.createMessageChannel(Worker.current);
			workerToMain.addEventListener(Event.CHANNEL_MESSAGE, onMessage);
			worker.setSharedProperty("mc", workerToMain);
			worker.setSharedProperty("bytes", bytes);
			worker.start();
		}
 
		private function startWorkerThread(): void
		{
			var mc:MessageChannel = Worker.current.getSharedProperty("mc");
			var bytes:ByteArray = Worker.current.getSharedProperty("bytes");
			mc.send(["Worker", bytes.length,bytes.position]);
		}
 
		private function onMessage(ev:Event): void
		{
			row.apply(null, (ev.target as MessageChannel).receive());
		}
	}
}

It prints this:

Reporter,Length,Position
Main,20,10
Worker,20,0

So the two workers are sharing the length field, which is very handy. However, they are unfortunately not sharing the position field. This can lead to some very strange behavior if you don’t know this. For example, all of the readX and writeX functions of ByteArray rely on the position field. For example:

bytes.position = 70;
bytes.readByte();
bytes.position == 71;

Since each worker has its own position, two workers can’t collaborate directly using these functions. That is, if worker A calls readByte it will not affect worker B’s position and worker B will get the same byte when it calls readByte. To work around this, you’ll need to pass the position field back and forth with either setSharedProperty and getSharedProperty or with MessageChannel.

Aside from this quirk of shared ByteArray instances, it should serve as your best shared data structure because of its shareable property. Other classes like Array, Vector, and Dictionary are all subject to expensive copying operations, so it’s best to use them sparingly or not at all.

Spot a bug? Have a question or suggestion? Post a comment!