用于在执行前检查异步任务依赖关系的设计模式 [已关闭]

Design pattern for checking asynchronous task dependencies before execution [closed]

提问人:stevendesu 提问时间:11/3/2017 最后编辑:Chetan Kingerstevendesu 更新时间:2/21/2018 访问量:1592

问:


想改进这个问题吗?更新问题,以便可以通过编辑这篇文章用事实和引文来回答。

5年前关闭。

问题

鉴于有许多异步加载的依赖项,我只想在所有依赖项完成加载后才触发一些代码。举个简单的例子,请考虑以下伪代码:

bool firstLoaded = false, secondLoaded = false, thirdLoaded = false;

function loadResourceOne() {
    // Asynchronously, or in a new thread:
    HTTPDownload("one.txt");
    firstLoaded = true;
    if (secondLoaded && thirdLoaded) {
        allLoaded();
    }
}

function loadResourceTwo() {
    // Asynchronously, or in a new thread:
    HTTPDownload("two.txt");
    secondLoaded = true;
    if (firstLoaded && thirdLoaded) {
        allLoaded();
    }
}

function loadResourceThree() {
    // Asynchronously, or in a new thread:
    HTTPDownload("three.txt");
    thirdLoaded = true;
    if (firstLoaded && secondLoaded) {
        allLoaded();
    }
}

function allLoaded() {
    Log("Done!");
}

/* async */ loadResourceOne();
/* async */ loadResourceTwo();
/* async */ loadResourceThree();

我在找什么

我发现自己不得不在不同的语言和不同的语境中反复解决这个问题。然而,每次我发现自己使用语言提供的工具来组合一些简单的解决方案时,比如在 JavaScript 中将每个异步资源作为 Promise 返回,然后使用 -- 或者在 Python 中将每个资源加载到它自己的线程中,然后使用Promise.all()threads.join()

我正在尝试找到一种在一般情况下解决此问题的设计模式。最佳解决方案应满足两个条件:

  1. 可应用于任何支持异步操作的语言
  2. 最大限度地减少代码的重复(请注意,在我的简单示例中,该行重复了三次,并且它前面的语句实际上是重复的,如果我需要第四个或第五个依赖项,则无法很好地扩展)allLoaded();if
  3. 加载所有资源后尽快运行最终回调 - 希望这是显而易见的,但是像“每 5 秒检查一次是否加载一次”这样的解决方案是不可接受的

我试着翻阅了四人帮设计模式的索引,但作为可能的线索跳出来的几个模式名称被证明是无关的。

多线程异 设计模式 语言无关

评论

0赞 Ravindra babu 11/4/2017
如果 java 是您的编程语言,请查看各种选项@ stackoverflow.com/questions/7939257/...

答:

0赞 Nrj 11/3/2017 #1

一个用依赖项数量初始化的闩锁,每个加载器在每次完成时都会递减它。

这样,一旦锁存器计数 = 0;我们知道所有函数都已加载,并且可以触发回调/所需的函数。

对于 Java - https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/CountDownLatch.html

4赞 jaco0646 11/3/2017 #2

您正在寻找 Fork-Join 模式

在并行计算中,分叉-连接模型是一种设置和执行并行程序的方法,使得执行在程序中的指定点并行分支,在后续点“加入”(合并)并恢复顺序执行。并行部分可能会递归分叉,直到达到某个任务粒度。分叉-联接可以看作是一种并行设计模式...

实现将取决于语言,但您可以结合您选择的语言搜索 fork-join。请注意,您不会在四人帮中找到异步模式。你会想要一本专门介绍多线程或并行计算的书。

评论

0赞 stevendesu 11/3/2017
一个简单的问题 -- Fork-Join 模式是否有通用的扩展来允许多个潜在的“子连接”?例如,在 wiki 页面上的图像中,假设并行任务 2,子任务 A 只需要并行任务 1 中的子任务 A 和 B(但不需要子任务 C)。有没有办法说“当 A 和 B 完成时,在任务 2 中启动 A - 即使 C 还没有准备好”?本质上是声明未来子任务的依赖关系,以便它们在能够启动的那一刻启动
0赞 Chetan Kinger 2/18/2018
@stevendesu 在您的问题中,您特别谈到了两个要求,即能够通知任务其依赖项的完成情况,以及无需添加新条件即可添加更多依赖项的能力。你还提到在四人帮模式中找不到任何合适的东西。以我的拙见。上述答案似乎没有涉及这些要点。为了其他用户,对问题进行编辑以表明您只是在寻找表示提交任务和等待结果的模式的名称会有所帮助。
0赞 stevendesu 2/19/2018
@CKing 虽然这个解决方案并不完全是我想要的,但它是我收到的唯一一个为进一步研究和探索提供设计模式的答案。虽然不理想,但许多程序可以重新架构以适应分叉-连接模式。如果您知道更好的模式,请发布答案,我很乐意切换接受的模式。通过我自己的研究,我得到的最好的是“未来/承诺”,它不是一种设计模式,而更像是一些编程语言的一个特性。
0赞 Chetan Kinger 2/19/2018
@stevendesu fork-join 模式只讨论程序如何并行执行并连接在一起以继续顺序执行。它不讨论建模/添加任务依赖关系和通知依赖关系。解决上述问题的解决方案将结合使用并发设计模式和面向对象设计模式。它不能只使用 fork-join 关键问题是,你想自己构建这个吗?如果没有,你想要的是一个已经解决这个问题的框架。如果是,则需要多个模式。请看我从这个角度写的答案。
3赞 Chetan Kinger 11/4/2017 #3

我试着翻阅了四人帮设计模式的索引,但作为可能的线索跳出来的几个模式名称被证明是无关的。

这个问题域将需要组合多个设计模式,而不是单个设计模式。让我们解决关键要求:

  1. 任务应该能够知道它所依赖的任务何时完成 以便它可以立即开始执行。这需要在没有 定期轮询相关任务。
  2. 向任务添加新的依赖项需要成为可能,而无需不断添加新的样式检查。if-else

对于第 1 点,我建议您看一下 Observer 模式。在你的情况下,此模式的主要优点是任务不必轮询其依赖任务。相反,任务所依赖的每个任务都会在任务完成时通过调用该方法通知任务。可以智能地实现该方法,以检查每次调用该方法时它所依赖的预填充任务列表。当所有预配置的任务列表都调用时,任务可以启动它的工作线程(例如线程)。updateupdateupdate

对于第 2 点,我建议您看一下复合模式。A 具有依赖实例和它所依赖的实例。如果任务完成执行,它将调用依赖于它的任务数组中的每个任务。另一方面,对于要开始执行的任务,它所依赖的其他任务将调用它的方法。TaskarrayTaskarrayTaskupdateupdate

如果我必须在伪代码中定义上述方法,它将如下所示:

Task structure :
   array of dependents : [dependent Task instances that depend on this Task]
   array of dependencies : [Task instances this task depends on]

   function update(Task t) : 
       remove t from dependencies
       if(dependencies size == 0) 
          - start asynchronous activity (call executeAsynchronous)

    function executeAsynchronous() :
        - perform asynchronous work
        - on completion :
             - iterate through dependent array
               - call update on each Task in dependent array and pass it this Task

    function addDependent(Task t) :
       - add t to array of dependent tasks

    function addDependency(Task t) :
       - add t to array of dependencies 

总而言之,不要去寻找设计模式来解决你的问题。相反,想出工作代码并完成它以改进其设计。


注意:框架和设计模式之间存在微小但显著的差异。如果目标是使用设计模式构建任务依赖框架,那么肯定需要多个设计模式。上面的答案解释了如何使用四人帮模式来做到这一点。如果目标是不重新发明轮子,可以看看已经解决了这个问题的框架。

Spring Batch 框架就是这样一个框架,它允许您定义顺序流和拆分流,这些流可以连接到定义端到端处理作业中。

评论

0赞 stevendesu 11/4/2017
我寻找设计模式的主要原因是,这感觉像是很多人以前遇到过的问题,并且一次又一次地得到解决。当应用标准化且易于理解的解决方案具有如此多的优势时,没有理由重新发明轮子。
0赞 Chetan Kinger 11/4/2017
我同意。重新发明轮子是一个很大的禁忌。出于这个原因,你应该问的是,是否有一个框架已经解决了这个问题,而不是要求一个设计模式来重新发明现有框架已经做的事情。框架设计模式之间存在微小但显著的差异。也就是说,我确实尝试合并了一些定义明确的设计模式,这些模式可以组合起来提出您自己的解决方案/框架。让我知道这是否有帮助/需要澄清。