Eventually, Flash Player will support Worker Threads to take advantage of multi-core CPUs, but that may be quite a while from now. Today’s article shows you how you can get some concurrency right now by faking threads. Read on for the technique!

As implied above, your AS3 code has to be single-threaded. However, you are free to concurrently run JavaScript in the browser while your AS3 runs. The ExternalInterface class makes it easy to talk between AS3 and JavaScript through simple function calls. So, if you want to have JavaScript do some heavy computations for you (and why not? it’s often faster) while your Flash app continues running, here’s the procedure:

  1. Set a callback for when JavaScript is done using ExternalInterface.addCallback. Let’s call it onResult.
  2. Use ExternalInterface.call to call a JavaScript function start
  3. So that the ExternalInterface.call function doesn’t block execution, start calls setInterval to do the actual work later in another function: doWork
  4. When doWork is done, it calls onResult on the Flash HTML object and passes it the result

While the JavaScript “thread” is working, the AS3 app can continue doing whatever it pleases. The following app has AS3 and JavaScript functions to do some heavy work: compute the sum of the first N square roots. Click the buttons to do this work with just Flash, just JavaScript, or both of them using the above pseudo-threading model.

Launch Pseudo-Threads Demo

package
{
	import flash.external.*;
	import flash.display.*;
	import flash.system.*;
	import flash.events.*;
	import flash.utils.*;
	import flash.text.*;
 
	[SWF(width=500,height=400,backgroundColor=0xEEEAD9)]
	public class PseudoThreads extends Sprite
	{
		private var __logger:TextField = new TextField();
		private function log(msg:*): void { __logger.appendText(msg + "\n"); }
 
		private static const TEXT_FORMAT:TextFormat = new TextFormat("_sans", 11);
		private static const NUM:int = 100000000;
		private static const INTERVAL:int = 1;
		private static const NEEDED_RESULTS:int = 20;
		private static const PAD:Number = 3;
		private var __numResults:int;
		private var __intervalName:uint;
		private var __beginTime:int;
 
		public function PseudoThreads()
		{
			stage.align = StageAlign.TOP_LEFT;
			stage.scaleMode = StageScaleMode.NO_SCALE;
 
			Security.allowDomain("*");
			ExternalInterface.addCallback("reportResult", onResult);
 
			makeButton("Flash Only", onFlash);
			makeButton("JavaScript Only", onJS);
			makeButton("Flash and JavaScript", onBoth);
 
			var about:TextField = new TextField();
			about.autoSize = TextFieldAutoSize.LEFT;
			about.defaultTextFormat = TEXT_FORMAT;
			about.htmlText = '<font color="#0071BB">'
				+ '<a href="http://JacksonDunstan.com/articles/1427">'
				+ 'JacksonDunstan.com'
				+ '</a></font>\n'
				+ 'September 2011';
			about.x = stage.stageWidth - PAD - about.width;
			about.y = PAD;
			addChild(about);
 
			__logger.y = this.height + PAD;
			__logger.autoSize = TextFieldAutoSize.LEFT;
			addChild(__logger);
		}
 
		private function makeButton(label:String, callback:Function): void
		{
			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);
		}
 
		private function onFlash(ev:MouseEvent): void
		{
			startFlash();
			start();
		}
 
		private function onJS(ev:MouseEvent): void
		{
			startJS();
			start();
		}
 
		private function onBoth(ev:MouseEvent): void
		{
			startFlash();
			startJS();
			start();
		}
 
		private function start(): void
		{
			__logger.text = "";
			__beginTime = getTimer();
			log("Getting " + NEEDED_RESULTS + " results...");
		}
 
		private function startFlash(): void
		{
			__intervalName = setInterval(onInterval, INTERVAL);
		}
 
		private function startJS(): void
		{
			ExternalInterface.call("start", NUM, INTERVAL);
		}
 
		private function onResult(val:Number): void
		{
			result("JavaScript", val);
		}
 
		private function onInterval(): void
		{
			result("Flash", sumOfSqrts(NUM));
		}
 
		private function result(from:String, val:Number): void
		{
			__numResults++;
			log("Result #" + __numResults + " from " + from + ": " + val);
			if (__numResults >= NEEDED_RESULTS)
			{
				var time:int = getTimer() - __beginTime;
				log("Time for " + NEEDED_RESULTS + " results: " + time);
 
				ExternalInterface.call("stop");
				if (__intervalName)
				{
					clearInterval(__intervalName);
					__intervalName = 0;
				}
				__numResults = 0;
			}
		}
 
		private function sumOfSqrts(n:int): Number
		{
			var total:Number = 0;
			for (var i:int; i < n; ++i)
			{
				total += Math.sqrt(i);
			}
			return total;
		}
	}
}

You can see that it is working by the following Activity Monitor screenshots. They show high CPU usage for “Shockwave Flash (Chrome Plug-In Host)” when Flash is working and “Google Chrome Renderer” when JavaScript is working.

Flash Only

Flash Only Activity Monitor Screenshot

JavaScript Only

JavaScript Only Activity Monitor Screenshot

Flash and JavaScript

Flash and JavaScript Activity Monitor Screenshot

Obviously, the performance you see will vary from browser to browser and depend heavily on your CPU’s features, such as number of cores, hyperthreading, etc. Here is the test environment I used:

  • Flex SDK (MXMLC) 4.5.1.21328, compiling in release mode (no debugging or verbose stack traces)
  • Release version of Flash Player 10.3.183.10
  • Google Chrome 14.0.835.186
  • 2.4 Ghz Intel Core i5
  • Mac OS X 10.7.1

I got these results:

Type Time
Flash Only 41285
JavaScript Only 26664
Flash and JavaScript 25285

Granted, the speedup is not even close to double despite my dual-core setup. Actually, I got about a 7% boost compared to pure JavaScript and a 40% boost when compared with pure AS3. However, there may be tasks that are more suited to this than the fake number crunching I set up. Further, it may work out better on quad-core machines or other operating systems or browsers. That said, it may also work out worse.

Any suggestions you can give on how to improve the “pseudo-threads” model would be appreciated. The above is certainly only a “rough draft” of what’s possible when running Flash and JavaScript at once and it’s likely that I’ve missed something that will make them work together better.