Hugoware

The product of a web developer with a little too much caffeine

Simulate Threading Using Javascript

with 9 comments

One of the cool things about .NET is how easy it is to create more than one thread for your application to run on. On a recent project I had to make many calls to different web services on our network. They were all identical, but each call took quite awhile, roughly around 2 or 3 seconds each.

Instead of doing one at a time, I created 10 separate threads and then merged the results together. Instead of taking around 20 seconds, the calls were reduced to the length of the slowest call. (Web Services also have an Asynchronous method to call a service, so that is an alternative as well).

So What Does This Have To Do With Javascript?

In Javascript, any long running process will cause a noticeable lag for a user. Buttons won’t respond, links don’t do anything, the screen may even turn white — clearly not the user experience we want to deliver.

Recently I was experimenting with joining records using jLinq. jLinq was doing fairly well with the records I was using – I had about 850 records to join against a handful (about 10) of other records. The process finished in around 250ms to 500ms. I was pretty satisfied — until I tried a different set of records…

A different set of records, around 90, crippled the browser. After about 8 seconds the browser finally released itself and we were back in business. Yikes.

Simulating A Thread

So what are the options here? Well unless someone has built threading into Javascript then we’re forced to come with a more creative solution — enter setTimeout.

If you’ve read some of my blog posts before, you know I’m a big fan of enclosures. Using Javascript we can take advantage of both enclosures and setTimeout to try and simulate threading and reduce the time a browser is locked up.

So let’s say we’re working with a really large loop, say around 500,000 records – What can we do? One idea is to break up the work into smaller, more manageable chunks.


//loops through an array in segments
var threadedLoop = function(array) {
	var self = this;
	
	//holds the threaded work
	var thread = {
		work: null,
		wait: null,
		index: 0,
		total: array.length,
		finished: false
	};
	
	//set the properties for the class
	this.collection = array;
	this.finish = function() { };
	this.action = function() { throw "You must provide the action to do for each element"; };
	this.interval = 1;
	
	//set this to public so it can be changed
	var chunk = parseInt(thread.total * .005);
	this.chunk = (chunk == NaN || chunk == 0) ? thread.total : chunk;
	
	
	//end the thread interval
	thread.clear = function() {
		window.clearInterval(thread.work);
		window.clearTimeout(thread.wait);
		thread.work = null;	
		thread.wait = null;
	};
	
	//checks to run the finish method
	thread.end = function() {
		if (thread.finished) { return; }
		self.finish();
		thread.finished = true;
	};
	
	//set the function that handles the work
	thread.process = function() {
		if (thread.index >= thread.total) { return false; }
	
		//thread, do a chunk of the work
		if (thread.work) {
			var part = Math.min((thread.index + self.chunk), thread.total);
			while (thread.index++ < part) {
				self.action(self.collection&#91;thread.index&#93;, thread.index, thread.total);
			}					
		}
		else {
		
			//no thread, just finish the work
			while(thread.index++ < thread.total) {
				self.action(self.collection&#91;thread.index&#93;, thread.index, thread.total);
			}	
		}
		
		//check for the end of the thread
		if (thread.index >= thread.total) { 
			thread.clear(); 
			thread.end();
		}
		
		//return the process took place
		return true;
		
	};
	
	//set the working process
	self.start = function() {
		thread.finished = false;
		thread.index = 0;
		thread.work = window.setInterval(thread.process, self.interval);
	};
	
	//stop threading and finish the work
	self.wait = function(timeout) {

		//create the waiting function
		var complete = function() {
			thread.clear();
			thread.process();
			thread.end();
		};
		
		//if there is no time, just run it now
		if (!timeout) {
			complete();
		}
		else {
			thread.wait = window.setTimeout(complete, timeout);
		}
	};

};

// Note: this class is not battle-tested, just personal testing on large arrays

This example class allows us to pass in a loop and then supply a few actions for us to use on each pass. The idea here is that if we do a section, pause for the browser to catch up, and then resume work.

How exactly do you use this? Well let’s just say we have a really large loop of strings we’re wanting to compare…

var array = [];
for (var i = 0; i < 500000; i++) { array.push("this is some long string"); } [/sourcecode] That's a lot of work to check all those - Let's move it into our new class we created... [sourcecode language='javascript'] //create our new class var work = new threadedLoop(array); //create the action to compare each item with work.action = function(item, index, total) { var check = (item == "this is some long string comparison to slow it down"); document.body.innerHTML = "Item " + index + " of " + total; }; //another action to use when our loop is done work.finish = function(thread) { alert("Thread finished!"); }; //and start our 'thread' work.start(); [/sourcecode] If you run this test in a browser, you'll see that our page is updated as each pass of the array is completed. This way our browser remains 'responsive' to a degree, but continues to process our work in the background. This code allows you to set a few additional properties as well as an additional function.

  • chunk: The number of records to loop through on each interval. The default is numberOfRecords * 0.005.
  • interval: The number of milliseconds to wait between passes. The default is 1. A longer value gives the browser more time to recover, but makes the loop take longer.
  • wait([timeout]): Waits the number of milliseconds before canceling the thread and blocking the browser until the work finishes. If no time is provided, as in left blank, the waiting starts immediately.

Threading Possibilities?

It’s always amazing to see what enclosures can do – I’m not so sure how simple this same creation would be in Javascript without them. With a little bit of code we can create a half-way decent attempt at creating an asynchronous experience for our user, even if we have a lot of work to do.

Could your heavy client side scripts benefit from ‘threading’?

Advertisements

Written by hugoware

July 10, 2009 at 6:05 am

9 Responses

Subscribe to comments with RSS.

  1. Simulate Threading Using Javascript « Yet Another WebDev Blog…

    Thank you for submitting this cool story – Trackback from DotNetShoutout…

    DotNetShoutout

    July 10, 2009 at 6:10 am

  2. I really liked your post. You layout the right level of detail while keeping my attention. Good stuff.

    Recently I have been doing more client side development, having been inspired by Dave Ward’s posts on jQuery and .Net. Here’s a link to a collection of some those posts. Scroll past the graphic and you’ll see them.

    ActiveEngine Sensei

    July 14, 2009 at 8:22 pm

  3. This is a fantastic piece of code!! I mean, I have to deal with arrays that are sized anywhere from 500 to 25000. This speed things up quite a bit.

    But I was perplexed that the work.action function skips every 25th item in my sample arrays sized, 472, 1756, and 267000. I tried setting the chunk size from 1 to 500, but non of them worked. And sometimes, when I execute the work.start() frequently enough, many of my results disppear mysteriously. By result, I mean the output of work.action(). I basically just do $(“result”).innerHTML += index+’ ‘. nothing fancy at all.

    Do you happen to know why this is the case? I googled it using firefox settimeout 25ms to have found some claimed that firefox can’t handle delayed functions that’s of less than 25ms of delay. Is this related?

    Thank YOU!

    N

    Nik

    September 21, 2009 at 4:10 am

    • Nik,

      Sorry for the late response – I’m going to check it out and get back with you. That is a really interesting problem. I wonder if it has anything to do with the segments alloted to do in each pass on the array. Are you performing your work in 25 elements at a time?

      webdev_hb

      September 24, 2009 at 9:36 pm

      • Right back at you with a late response– which I am sorry about. — Thank you for replying!

        The situation hasn’t improved. And to answer your question”whether I am processing 25 elements in the large array at a time with work.action”. I am not because I am using that scaling factor you offer as a config. Like 0.005 is what I use. For instance, for that 26700 objects array, so I do about 133 elements at a time.

        But you get the idea, right, because I am just outputting the index to some empty div, so it should be straight forward like light travels in a straight line. okay, bad example, but it is good for diagnostics.

        Hey, I found this article, by John Resig explaining something about settimeout won’t fire if is overlapped? It is very very interesting. I might have something to do with this

        http://ejohn.org/blog/how-javascript-timers-work/

        Thanks again! And this time I am signing up for the follow-up email notify so I know you have written so that I don’t delay like this.

        Cheers!

        Nik

        May 13, 2010 at 2:35 pm

  4. Here’s a MooTools implementation of the above concept with a few changes: http://mootools.net/forge/p/threaded_loop

    Chris the Developer

    March 11, 2010 at 6:05 am

    • Very nice – The pause and resume feature is especially impressive. Good work!

      hugoware

      March 11, 2010 at 6:24 pm

  5. […] […]

    Anonymous

    October 24, 2010 at 8:44 am


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: