Concurrency

Eric McGary edited this page Feb 1, 2013 · 5 revisions

Out of date - Will be updated soon!

Check out the live docs for the most up to date functionality until then.


When you set the `concurrent` option for a task to `true`, which is `false` by default, the library does some things behind the scenes and runs the task's `performTask` functionality within a web worker on a parallel thread available through a web `Worker`. Let's take the following task as an example. If you were to run this example on the main application thread it would innevitably cause the screen to lock up (if it didnt timeout first).

	var task = new MonkeyBars.Task({
		logLevel:1000,
		concurrent:true,
		performTask:function(){
			for ( var i = 0; i < 800000000; i++ ) {
				if("a" != "b" && "b" != "c" && "c" == "d") return;
			}
			this.complete();
		}
	});

	task.start();

You'll notice that the concurrent task option is set to true. The library copies the functionality referenced within the performTask method and works it into an HTML5 Blob. Then it takes that Blob and uses it to create an HTML5 Worker who's functionality will be run on a seperate thread.

It's important to note that the only code that is run on a seperate thread is the performTask method, all other functionality is still performed on the main UI thread of the browser. It is also important to note that as of right now only tasks of type simple (MonkeyBars.TaskTypes.Simple) will run their performTask functionality on a seperate thread. This is mainly due to the fact that both sequence and parallel task types are for the most part queues that execute subtasks.

When creating a task, that by design is to run concurrently, the task should contain everything that it needs to fully execute independently from any other sibling tasks. So for example if the task makes use of some globally avaiable object, you should make reference to that object and write that reference to the task object, else it will fail. Once the task is complete, it can pass any data that it may have manipulated during it's execution through the complete method, which is then comsumed by its, or it's group's, handleProduct method. The following two example, building on the first, illustrates what is meant by this...


	var task = new MonkeyBars.Task({
		logLevel:1000,
		concurrent:true,
		performTask:function(){
			var index = 0;
			for ( var i = 0; i < 10000; i++ ) {
				index++;
			}
			this.complete(index);
		},
		handleProduct:function(product){
			console.log(product); // should output 10000
		}
	});

	task.start();

And here is an example of a task manipulating the product of the group...


	var task = new MonkeyBars.SequenceTask({
		concurrent:true,
		product:0,
		tasks:[
			{
				performTask:function(){
					for ( var i = 0; i < 5000; i++ ) {
						this.product++;
					}
					this.complete(this.product);
				}
			},
			{
				performTask:function(){
					for ( var i = 0; i < 5000; i++ ) {
						this.product++;
					}
					this.complete(this.product);
				}
			}
		],
		handleProduct:function(product){
			console.log(product); // should output 10000
		}
	});

	task.start();

The final note, which is probably the most important, is that tasks run concurrently can only call certain prototype methods within the performTask method. These include postMessage (a helper function for the potMessage functionality available to web workers), complete, fault and cancel as well as simple console.log statments (only tested with strings as of now). This is important to note because there are many instances in which you could be writing seperate methods on a task instance or brand new methods if extending a task. The following example shows what is meant by this...


	var task = new MonkeyBars.Task({
		logLevel:1000,
		concurrent:true,
		performTask:function(){
			var index = 0;
			for ( var i = 0; i < 10000; i++ ) {
				index++;
			}
			this.myCustomFunction(index); // this will fail
		},
		handleProduct:function(product){
			console.log(product); // should output 10000
		},
		myCustomFunction:function(arg){
			this.complete(arg); // complete will never be run
		}
	});

	task.start();

You should see an error in the console that reads something similar to this Uncaught TypeError: Object #<Object> has no method 'myCustomFunction'. If the task being run concurrently needs more functionality than the three methods listed above then it will need to implement its own custom WorkerTask (which is extendable). The following example shows how this could be possible...


	var CustomWorker = MonkeyBars.WorkerTask.extend({
		append:function(product){
			this.postMessage("append",100);
			this.devide(product + 100);
		},
		devide:function(product){
			this.postMessage("devide",2);
			this.complete(product/2);
		}
	});

	var index = 0;

	var task = new MonkeyBars.Task({
		concurrent:true,
		worker:{
			constructor:CustomWorker,
			handler:function(e){
				if(e.data.type === "append") {
					index += e.data.value;
				} else if(e.data.type === "devide") {
					index = index / e.data.value;
				}
			}
		},
		performTask:function(){
			this.append(0);
		}
	});

	task.start();

Notice the additional worker attribute written to the task object instance. This basically tells the library that it should use the listed extended WorkerTask constructor instead of the predifined one provided by the library. Also notice that there is a handler key value pair applied to this attribute. This function is run everytime the postMessage WorkerTask helper function is run and this function executes on the main thread of the application.

An important note is that currently sub tasks of a group inherit their groups concurrency. This is something that will be availbale in future versions, just not yet available.