使用 JavaScript 模拟多线程






4.27/5 (6投票s)
使用 JavaScript 模拟多线程。
关于 .NET,其中一件很酷的事情是为应用程序创建多个线程以便运行是多么容易。在最近的一个项目中,我必须对我们的网络上的不同 Web 服务进行多次调用。它们都相同,但每次调用都需要相当长的时间,大约 2 到 3 秒。
我没有一次只做一件事情,而是创建了 10 个单独的线程,然后将结果合并在一起。调用时间从大约 20 秒减少到最慢调用的时间。*(Web 服务也有一个异步方法来调用服务,所以这也是一种选择)*。
那么这与 JavaScript 有什么关系呢?
在 JavaScript 中,任何长时间运行的进程都会导致用户出现明显的滞后。按钮将无法响应,链接没有任何作用,屏幕甚至可能会变成白色——这显然不是我们想要提供的用户体验。
最近,我一直在尝试使用 jLinq 连接记录。jLinq 对我正在使用的记录表现良好——我大约有 850 条记录要与少数(大约 10 条)其他记录连接。该过程在大约 250 毫秒到 500 毫秒内完成。我非常满意——直到我尝试了另一组记录……
另一组大约 90 条记录,使浏览器瘫痪了。大约 8 秒后,浏览器终于恢复了,我们又可以工作了。哎呀。
模拟线程
那么这里的选择是什么?好吧,除非有人在 JavaScript 中构建了线程,否则我们不得不提出一个更具创造性的解决方案——输入 setInterval
。
如果您之前阅读过我的一些博文,您就会知道我非常喜欢闭包。使用 JavaScript,我们可以利用闭包和 setInterval
来尝试模拟线程并减少浏览器被锁定的时间。
那么,假设我们正在处理一个非常大的循环,比如说大约 500,000 条记录——我们能做什么?一个想法是将工作分解成更小、更易于管理的部分。
//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[thread.index], thread.index, thread.total);
}
}
else {
//no thread, just finish the work
while(thread.index++ < thread.total) {
self.action(self.collection[thread.index], 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
这个示例类允许我们传入一个循环,然后提供一些操作供我们在每次传递中使用。这里的想法是,如果我们完成一部分,暂停让浏览器赶上,然后恢复工作。
您究竟如何使用它?嗯,让我们假设我们有一个非常大的 string
循环,我们想要进行比较……
var array = [];
for (var i = 0; i < 500000; i++) {
array.push("this is some long string");
}
这需要做很多工作才能检查所有这些——让我们把它移到我们创建的新类中……
//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();
如果您在浏览器中运行此测试,您将看到我们的页面在数组的每次传递完成后都会更新。这样,我们的浏览器在某种程度上保持“响应”,但继续在后台处理我们的工作。
此代码允许您设置一些其他属性以及一个附加函数。
- chunk:在每个间隔内循环的记录数。默认值为
numberOfRecords * 0.005
。 - interval:在每次传递之间等待的毫秒数。默认值为
1
。较长的值为浏览器提供了更多恢复时间,但会使循环花费更长的时间。 - wait([timeout]):等待指定的毫秒数,然后取消线程并阻止浏览器,直到工作完成。如果未提供时间,例如留空,则 等待 立即开始。
线程可能性?
看到闭包可以做什么总是令人惊叹的——我不确定如果没有它们,在 JavaScript 中创建同样简单的事情会怎么样。通过少量代码,我们可以创建一个半体面的尝试,为我们的用户创建异步体验,即使我们有很多工作要做。
您的繁重的客户端脚本是否可以从“线程”中受益?