【问题标题】:Task Runner in JavascriptJavascript 中的任务运行器
【发布时间】:2022-10-09 01:01:22
【问题描述】:

我有一个任务列表,只有在为每个任务解决了所有依赖项之后,才需要执行所有这些任务。我正在努力寻找一种在最佳时间完成所有任务的方法。

// Each node is a async job, illustrated by setTimeout.
// A and C can run at the same time.
// D, needs to wait for A and C to be done.
// E needs to wait for A and D to be done.

function runTasks(tasks) {
 // run tasks
}

// Sample of tasks
var tasks = {
  'a': {
    job: function (finish) {
      setTimeout(function () {
        console.log('a done');
        finish();
      }, 500);
    },
  },
  'c': {
    job: function (finish) {
      setTimeout(function () {
        console.log('c done');
        finish();
      }, 200);
    },
    dependencies: [],
  },
  'd': {
    job: function (finish) {
      setTimeout(function () {
        console.log('d done');
        finish();
      }, 100);
    },
    dependencies: ['a','c'],
  },
  'e': {
    job: function (finish) {
      setTimeout(function () {
        console.log('e done');
        finish();
      }, 200);
    },
    dependencies: ['a', 'd'],
  },
};

【问题讨论】:

  • “A、B、C 可以同时运行。”他们是这样?请分享如何,因为 afaik JavaScript 是单线程的,一次只能运行一件事。 - 另外,你已经标记了承诺,但它们没有被使用。
  • 为每个任务设置一个布尔值。让依赖于其他任务的任务按间隔而不是超时运行,并检查布尔值是否设置为 true。所以说 boolA = false。当任务 A 完成时,它将 boolA 设置为 true。函数 C 检查 boolA 和 boolB 是否都设置为 true。如果没有,它就不会运行并继续其间隔循环。如果是,它会运行并在之后取消自己的间隔,因此它不会再次运行。继续这样
  • 上面的代码 sn-p 只是一个示例 sn-p,可以修改它以使用异步编程(async/await)
  • @evolutionxbox 好吧,是的,也不是。虽然 JS 是单线程的,但它确实有回调函数。这意味着它只是启动它们并继续运行其他任务,直到回调函数“回调它”。就像超时是一个回调函数一样,它只会运行其他任务,直到超时用完并回调它。如果其他任务需要在超时完成后运行,这可能是个问题。所以你,不是“同时”运行,但绝对是异步的
  • @icecub 使用 setInterval 将不满足“最佳时间”的要求,因为间隔会导致延迟。

标签: javascript asynchronous async-await promise


【解决方案1】:

您可以启动可以启动的并行承诺。

  1. 首先启动所有没有依赖关系的独立任务
  2. 完成独立任务后存储在地图/集合中
  3. 然后使用完整的重复步骤过滤一组新任务。

    const wait = (ms, task) =>
      new Promise((resolve) =>
        setTimeout(() => {
          console.log(`${task} done`);
          resolve();
        }, ms)
      );
    
    const tasks = {
      a: {
        job: () => wait(500, "A"),
        dependencies: [],
      },
      c: {
        job: () => wait(500, "C"),
        dependencies: [],
      },
      d: {
        job: () => wait(500, "D"),
        dependencies: ["a", "c"],
      },
      e: {
        job: () => wait(500, "E"),
        dependencies: ["a", "d"],
      },
    };
    
    const run = async () => {
      const completed = new Set();
      const keys = Object.keys(tasks);
      const runTasks = async (pendingTasks) => {
        const promises = pendingTasks.map((key) => tasks[key].job());
        await Promise.all(promises);
        pendingTasks.forEach((key) => completed.add(key));
      };
    
      const runner = async () => {
        const pendingTasks = keys.filter((key) => {
          if (!completed.has(key)) {
            const { dependencies } = tasks[key];
            return dependencies.every((dependency) => completed.has(dependency));
          }
          return false;
        });
        if (pendingTasks.length !== 0) {
          await runTasks(pendingTasks);
          return runner();
        }
      };
    
      await runner();
    };
    
    run();

【讨论】:

    【解决方案2】:

    一个简单的带有结果缓存的递归依赖评估就可以了。使用Promise.all 等待所有依赖项:

    function runTasks(tasks) {
      const promises = {};
      function runTask(name) {
        const {dependencies, job} = tasks[name];
        return promises[name] ??= Promise.all(dependencies.map(runTask)).then(job);
      }
      return Promise.all(Object.keys(tasks).map(runTask));
    }
    

    也许在最后的return 语句中使用Promise.allSettled 而不是Promise.all

    如果需要检测循环依赖,可以使用

    …
      const circular = Promise.reject(new Error("circular dependency"));
      circular.catch(() => { /* ignore */ }); // prevent unhandled rejection
      function runTask(name) {
        const {dependencies, job} = tasks[name];
        if (promises[name] != null) return promises[name];
        promises[name] = circular;
        const jobArgs = Promise.all(dependencies.map(runTask));
        return promises[name] = jobArgs.then(job);
      }
    …
    

    【讨论】:

    • 我不明白循环依赖检测是如何工作的,你能解释一下吗?就像你最后双重分配promises[name] ,不知道这将如何工作......
    • @Joji 首先,circular(一个被拒绝的承诺,当你尝试依赖它时会传播错误)被分配为值,然后 dependencies.map(runTask) 被评估(它递归地遍历依赖项和可能遇到circular sentinel 值),最后将结果承诺分配为值。
    • 我仍然不太明白为什么最后有两个promises[name] = 分配。 ?
    • @Joji 我现在添加了一个额外的行,这有帮助吗?
    【解决方案3】:

    这样的事情可能是可能的,我已将作业更改为异步方法,并跟踪它们是否已解决。

    此代码不检查作业中的错误或循环依赖关系。

    脚本的输出:

    b done
    a done
    c done
    e done
    done
    

    代码:

    const runTasks = async tasks => {
      const taskArray = Object.entries(tasks)
      const resolved = {}
    
      while (taskArray.length) {
        const tasksToExecute = []
        for (const task of taskArray) {
          const dependencies = task[1].dependencies || []
          if (dependencies.length === 0 || dependencies.every(dependency => resolved[dependency] || false)) {
            tasksToExecute.push(task)
          }
        }
    
        await Promise.all(tasksToExecute.map(t => t[1].job()))
        tasksToExecute.forEach(t => {
          resolved[t[0]] = true
          taskArray.splice(taskArray.indexOf(t[0]), 1)
        })
      }
    }
    
    const tasks = {
      a: {
        job: async function () {
          return new Promise(resolve => {
            setTimeout(() => {
              console.log("a done")
              resolve()
            }, 500)
          })
        },
      },
      c: {
        job: async function () {
          return new Promise(resolve => {
            setTimeout(() => {
              console.log("c done")
              resolve()
            }, 200)
          })
        },
        dependencies: ["a", "b"],
      },
      b: {
        job: async function () {
          return new Promise(resolve => {
            setTimeout(() => {
              console.log("b done")
              resolve()
            }, 100)
          })
        },
        dependencies: [],
      },
      e: {
        job: async function () {
          return new Promise(resolve => {
            setTimeout(() => {
              console.log("e done")
              resolve()
            }, 200)
          })
        },
        dependencies: ["c", "b"],
      },
    }
    
    runTasks(tasks).then(r => console.log("done"))
    

    【讨论】:

    • 看起来这不是最佳解决方案,因为 a 和 b 没有任何依赖关系并且可以并行启动
    • 查看更新的答案
    • 观察:使用 Promise.all 意味着所有依赖项必须在新的符合条件的任务开始之前完成。例如,添加一个依赖于 'c' 的新任务 'f',它不会在 'a' 完成之前启动。
    【解决方案4】:

    这使用Promise.race 独立于其他人处理任务。使用Promise.all 并不是最佳选择,因为它会等待所有相关任务完成,然后再评估是否可以启动另一个任务。

    我认为这是最佳的时间。它可能具有更高的处理开销,因为所有作业都保存在单个对象中,并且可能通过将作业移动到已完成的对象来更好地处理以避免重新评估已完成的任务。

    注意:这不会评估循环依赖关系,或者依赖关系实际上是任务。

    "use strict";
    class Job {
        constructor(getJobs, name, task) {
            this.getJobs = getJobs;
            this.name = name;
            this.task = task;
        }
        isWaiting() {
            return this.promise !== undefined;
        }
        isComplete() {
            return 'result' in this;
        }
        isBlocked() {
            return !(this.task.dependencies || []).map(name => this.getJobs()[name]).every(job => job.isComplete()) || false;
        }
        isElligible() {
            return !this.isWaiting() && !this.isComplete() && !this.isBlocked();
        }
    }
    async function runTasks(tasks) {
        const jobs = Object.entries(tasks)
            .reduce((acc, [name, task]) => {
            acc[name] = new Job(() => jobs, name, task);
            return acc;
        }, {});
        while (true) {
            Object.keys(jobs).forEach(name => {
                if (jobs[name].isElligible()) {
                    console.log(`${name} elligible`);
                    jobs[name].promise = new Promise(resolve => {
                        console.log(`${name} starting`);
                        jobs[name].task.job(resolve);
                    }).then(res => {
                        jobs[name].result = res;
                        delete jobs[name].promise;
                    });
                }
            });
            const pending = Object.values(jobs).filter(job => job.isWaiting());
            console.log('racing', pending.map(j => j.name));
            if (pending.length) {
                await Promise.race(pending.map(job => job.promise));
            }
            else {
                break;
            }
        }
    }
    var tasks = {
        'a': {
            job: function (finish) {
                setTimeout(function () {
                    console.log('a done');
                    finish();
                }, 500);
            },
        },
        'c': {
            job: function (finish) {
                setTimeout(function () {
                    console.log('c done');
                    finish();
                }, 200);
            },
            dependencies: [],
        },
        'd': {
            job: function (finish) {
                setTimeout(function () {
                    console.log('d done');
                    finish();
                }, 100);
            },
            dependencies: ['a', 'c'],
        },
        'e': {
            job: function (finish) {
                setTimeout(function () {
                    console.log('e done');
                    finish();
                }, 200);
            },
            dependencies: ['a', 'd'],
        },
        'f': {
            job: function (finish) {
                setTimeout(function () {
                    console.log('f done');
                    finish();
                }, 500);
            },
            dependencies: ['c'],
        },
    };
    runTasks(tasks).then(() => console.log('ALL done'));

    【讨论】:

      猜你喜欢
      • 2014-06-02
      • 1970-01-01
      • 1970-01-01
      • 2020-11-18
      • 1970-01-01
      • 2015-09-23
      • 2011-10-15
      • 2019-08-01
      • 1970-01-01
      相关资源
      最近更新 更多