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

时间:2017-11-02 18:25:50

标签: multithreading asynchronous design-patterns language-agnostic

问题

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

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();

我正在寻找

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

我试图在一般情况下找到解决此问题的设计模式。最佳解决方案应符合两个标准:

  1. 可以应用于任何支持异步操作的语言
  2. 最大限度地减少代码的重复(请注意,在我的简单示例中,行allLoaded();重复了三次,并且前面的if语句几乎重复,并且不会# 39;如果我需要第四或第五依赖,那么我可以很好地扩展)
  3. 在加载所有资源时尽快运行最终回调 - 这个很有希望显而易见,但是"等解决方案会检查所有资源是否每5秒加载一次#34;不可接受
  4. 我尝试翻阅四人组的设计模式的索引,但是跳出来的少数模式名称被证明是无关的。

3 个答案:

答案 0 :(得分:4)

您正在寻找Fork-Join pattern

  

在并行计算中,fork-join模型是一种设置和执行并行程序的方法,使得执行在程序中的指定点并行分支,以及#34; join" (合并)在后续点并继续执行顺序执行。并行部分可以递归地分叉,直到达到某个任务粒度。叉连接可以被认为是并行设计模式......

实现将依赖于语言,但您可以结合您选择的语言搜索fork-join。请注意,您不会在Gang of Four中找到异步模式。您可能需要一本专门针对多线程或并行计算的书。

答案 1 :(得分:2)

  

我尝试翻阅四人组的设计模式的索引,但是跳出来的少数模式名称被证明是无关的。

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

  1. 任务应该能够知道它所依赖的任务何时完成 这样它就可以立即开始执行。这需要在没有的情况下实现 定期轮询相关任务。
  2. 无需继续添加新的if-else样式检查,就可以在任务中添加新的依赖项。
  3. 对于第1点,我建议您查看Observer pattern。在您的情况下,这种模式的主要优点是任务不必轮询它的依赖任务。相反,您的任务所依赖的每个任务都会在完成后通过调用update方法通知您的任务。 update方法可以智能地实现,以检查预先填充的任务列表,它依赖于每次调用方法时所依赖的任务。当所有预先配置的任务列表调用update时,该任务可以启动它的工作者(例如一个线程)。

    对于第2点,我建议您查看Composite patternTask有一个array个依赖Task个实例和一个arrayTask个实例。如果任务完成执行,则会对依赖于该任务的任务数组中的每个任务调用update。另一方面,对于开始执行的任务,其依赖的其他任务将称之为update方法。

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

    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 
    

    所有人说完了,不要去寻找解决问题的设计模式。相反,提出工作代码并通过它来改进其设计。

    注意:框架与设计模式之间存在细微差别。如果目标是使用设计模式构建任务依赖框架,那么您肯定需要多个设计模式。上面的答案解释了如何使用Gang of Four模式执行此操作。如果目标是不重新发明轮子,可以看看已经解决了这个问题的框架。

    一个这样的框架是 Spring Batch 框架,它允许您定义sequential flowssplit flows,它们可以连接在一起定义结束的job结束处理流程。

答案 2 :(得分:0)

如何使用多个依赖项初始化一个锁存器,并且每个加载器在每次完成时都会减少它。

这种方式一旦锁存计数= 0;我们知道所有都已加载并且可以触发回调/所需功能。

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