提问人:stevendesu 提问时间:11/3/2017 最后编辑:Chetan Kingerstevendesu 更新时间:2/21/2018 访问量:1592
用于在执行前检查异步任务依赖关系的设计模式 [已关闭]
Design pattern for checking asynchronous task dependencies before execution [closed]
问:
问题
鉴于有许多异步加载的依赖项,我只想在所有依赖项完成加载后才触发一些代码。举个简单的例子,请考虑以下伪代码:
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()
我正在尝试找到一种在一般情况下解决此问题的设计模式。最佳解决方案应满足两个条件:
- 可应用于任何支持异步操作的语言
- 最大限度地减少代码的重复(请注意,在我的简单示例中,该行重复了三次,并且它前面的语句实际上是重复的,如果我需要第四个或第五个依赖项,则无法很好地扩展)
allLoaded();
if
- 加载所有资源后尽快运行最终回调 - 希望这是显而易见的,但是像“每 5 秒检查一次是否加载一次”这样的解决方案是不可接受的
我试着翻阅了四人帮设计模式的索引,但作为可能的线索跳出来的几个模式名称被证明是无关的。
答:
一个用依赖项数量初始化的闩锁,每个加载器在每次完成时都会递减它。
这样,一旦锁存器计数 = 0;我们知道所有函数都已加载,并且可以触发回调/所需的函数。
对于 Java - https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/CountDownLatch.html
您正在寻找 Fork-Join 模式。
在并行计算中,分叉-连接模型是一种设置和执行并行程序的方法,使得执行在程序中的指定点并行分支,在后续点“加入”(合并)并恢复顺序执行。并行部分可能会递归分叉,直到达到某个任务粒度。分叉-联接可以看作是一种并行设计模式...
实现将取决于语言,但您可以结合您选择的语言搜索 fork-join。请注意,您不会在四人帮中找到异步模式。你会想要一本专门介绍多线程或并行计算的书。
评论
我试着翻阅了四人帮设计模式的索引,但作为可能的线索跳出来的几个模式名称被证明是无关的。
这个问题域将需要组合多个设计模式,而不是单个设计模式。让我们解决关键要求:
- 任务应该能够知道它所依赖的任务何时完成 以便它可以立即开始执行。这需要在没有 定期轮询相关任务。
- 向任务添加新的依赖项需要成为可能,而无需不断添加新的样式检查。
if-else
对于第 1 点,我建议您看一下 Observer 模式。在你的情况下,此模式的主要优点是任务不必轮询其依赖任务。相反,任务所依赖的每个任务都会在任务完成时通过调用该方法通知任务。可以智能地实现该方法,以检查每次调用该方法时它所依赖的预填充任务列表。当所有预配置的任务列表都调用时,任务可以启动它的工作线程(例如线程)。update
update
update
对于第 2 点,我建议您看一下复合模式。A 具有依赖实例和它所依赖的实例。如果任务完成执行,它将调用依赖于它的任务数组中的每个任务。另一方面,对于要开始执行的任务,它所依赖的其他任务将调用它的方法。Task
array
Task
array
Task
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
总而言之,不要去寻找设计模式来解决你的问题。相反,想出工作代码并完成它以改进其设计。
注意:框架和设计模式之间存在微小但显著的差异。如果目标是使用设计模式构建任务依赖框架,那么肯定需要多个设计模式。上面的答案解释了如何使用四人帮模式来做到这一点。如果目标是不重新发明轮子,可以看看已经解决了这个问题的框架。
Spring Batch 框架就是这样一个框架,它允许您定义顺序流和拆分流,这些流可以连接到定义端到端处理流的作业中。
评论