Let us consider we need to create an aync task runner in javascript with the following constraint:
- The task runner should have a set of tasks that will be pushed to it,
- Each of these tasks will be some async operation,
- The task runner should also ensure that at a single point of time only a given number of tasks can perform, and other tasks keep on waiting unless their turn comes.
Let's code out the solution first
class Runner{
constructor(concurrency=1){
this.concurrency = concurrency;
this.waitList = [];
this.count = 0;
this.currentQ = [];
}
push(task){
this.waitList.push(task);
this.run();
}
run(){
let current = this;
if(this.count<this.concurrency){
this.count++;
if(this.waitList.length>0){
let task = this.waitList.shift();
let id = task.id;
this.currentQueue.push(id);
this.showRunningTasks();
let done = function(){
this.currentQueue.splice(this.currentQueue.indexOf(id),1);
this.showRunningTasks();
this.count = this.count - 1;
this.run();
}.bind(current);
task.task(done);
}
}
}
showRunningTasks(){
let existingQueue = this.currentQueue.join(", ");
document.getElementId("running").innerHTML = existingQueue;
}
}
Let's understand this piece of code line by line.
Initially we pass the
concurrency
flag indicating how many tasks can run concurrently. ThewaitList
is used for making a wait list of tasks, which will wait unless the queue referred to ascurrentQueue
is empty. Thecount
variable is initialised to count the number of concurrent tasks at a moment.The
push
method is used to push a task towaitList
, and we then execute the run method.This simply indicates that the tasks need to be run if the queue is emptyThe run method is interesting. First it preserves the context of the task by the virtue of first line denoted as
let current = this
Next, we check for the concurrency and if the waitList has elements, we simply,
- push the task id to the currentQueue,
- display the current running tasks via the method
showRunningTasks
, - in the inner
done
method, which will be taken as a callback in tasks, we simply remove the task with the id from current
queue, reduce the count to reduce the total number of concurrent tasks, - In the end, we recursively call the run() method to run the current function. Note, here the context remains same.
- Lastly we bind this function to the current context.
- We pass this function to task.task function.
Let's now see how this method can be used
Use the following code snippet to see the result.
let runner = new Runner(3);
let task1 = {
id: 1,
task: function(done) {
setTimeout(function() {
console.log("Task 1");
done();
}, 3000)
}
}
let task2 = {
id: 2,
task: function(done) {
setTimeout(function() {
console.log("Task 2");
done();
}, 5000)
}
}
let task3 = {
id: 3,
task: function(done) {
setTimeout(function() {
console.log("Task 3");
done();
}, 4000)
}
}
let task4 = {
id: 4,
task: function(done) {
setTimeout(function() {
console.log("Task 4");
done();
}, 9000)
}
}
runner.push(task1);
runner.push(task2);
runner.push(task3);
runner.push(task4);
You will see the result on the screen as Task 1, Task 3, Task 2, Task 4, which is expected.
Top comments (4)
Great thank you.
Perhaps could you be interested at the Communicating Sequential Process Cispy
Here is a little demo CspTaskRunner
Regards
ๆไธช็ผ
Amazing article. Wonderfully explained :)
Thank you so much.