One of the cool things about .NET is how easy it is to create more than one thread for your application to run on. In 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 a while, 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, we’re forced to come with a more creative solution — enter setInterval
.
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 setInterval
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.
var threadedLoop = function(array) {
var self = this;
var thread = {
work: null,
wait: null,
index: 0,
total: array.length,
finished: false
};
this.collection = array;
this.finish = function() { };
this.action = function() { throw "You must provide the action to do for each element"; };
this.interval = 1;
var chunk = parseInt(thread.total * .005);
this.chunk = (chunk == NaN || chunk == 0) ? thread.total : chunk;
thread.clear = function() {
window.clearInterval(thread.work);
window.clearTimeout(thread.wait);
thread.work = null;
thread.wait = null;
};
thread.end = function() {
if (thread.finished) { return; }
self.finish();
thread.finished = true;
};
thread.process = function() {
if (thread.index >= thread.total) { return false; }
if (thread.work) {
var part = Math.min((thread.index + self.chunk), thread.total);
while (thread.index++ < part) {
self.action(self.collection[thread.index], thread.index, thread.total);
}
}
else {
while(thread.index++ < thread.total) {
self.action(self.collection[thread.index], thread.index, thread.total);
}
}
if (thread.index >= thread.total) {
thread.clear();
thread.end();
}
return true;
};
self.start = function() {
thread.finished = false;
thread.index = 0;
thread.work = window.setInterval(thread.process, self.interval);
};
self.wait = function(timeout) {
var complete = function() {
thread.clear();
thread.process();
thread.end();
};
if (!timeout) {
complete();
}
else {
thread.wait = window.setTimeout(complete, timeout);
}
};
};
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 string
s we’re wanting to compare…
var array = [];
for (var i = 0; i < 500000; i++) {
array.push("this is some long string");
}
That’s a lot of work to check all those – Let’s move it into our new class we created…
var work = new threadedLoop(array);
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;
};
work.finish = function(thread) {
alert("Thread finished!");
};
work.start();
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’?