基本使用
条件任务评估一组指令,并返回要执行的下一个后续任务的整数索引。
该索引是根据其继任者结构的顺序定义的,首先,创建一个最简单的if else条件模块:
#include <taskflow/taskflow.hpp>
int main() {
tf::Taskflow taskflow;
tf::Executor executor;
auto [init, cond, yes, no] = taskflow.emplace(
[](){ std::cout << "init\n"; },
[](){ return std::rand()%2; }, // 返回后继者的索引
[](){ std::cout << "yes\n"; },
[](){ std::cout << "no\n"; }
);
cond.succeed(init).precede(yes, no);
executor.run(taskflow).wait();
taskflow.dump(std::cout);
}
注意: 对于cond节点后继的索引正确性,需要依靠用户来保证,如果cond返回的索引超过了合法区间,这个executor将不会调度任务任务。
当然,稍加修改节点的依赖,就可以实现循环逻辑:
#include <taskflow/taskflow.hpp>
int main() {
tf::Taskflow taskflow;
tf::Executor executor;
auto [init, cond, stop] = taskflow.emplace(
[](){ std::cout << "init\n"; },
[](){ std::cout << "flipping a coin\n"; return std::rand()%2; }, // 返回后继者的索引
[](){ std::cout << "stop\n"; }
);
// 循环结构, cond 在 init 之后,在cond,stop之前
cond.succeed(init).precede(cond, stop);
executor.run(taskflow).wait();
taskflow.dump(std::cout);
}
这种设计方式可以通过简单的代码实现相当复杂的任务逻辑:
#include <taskflow/taskflow.hpp>
int main() {
tf::Taskflow taskflow;
tf::Executor executor;
tf::Task A = taskflow.emplace([](){}).name("A");
tf::Task B = taskflow.emplace([](){}).name("B");
tf::Task C = taskflow.emplace([](){}).name("C");
tf::Task D = taskflow.emplace([](){}).name("D");
tf::Task E = taskflow.emplace([](){}).name("E");
tf::Task F = taskflow.emplace([](){}).name("F");
tf::Task G = taskflow.emplace([](){}).name("G");
tf::Task H = taskflow.emplace([](){}).name("H");
tf::Task I = taskflow.emplace([](){}).name("I");
tf::Task K = taskflow.emplace([](){}).name("K");
tf::Task L = taskflow.emplace([](){}).name("L");
tf::Task M = taskflow.emplace([](){}).name("M");
tf::Task cond_1 = taskflow.emplace([](){ return std::rand()%2; }).name("cond_1");
tf::Task cond_2 = taskflow.emplace([](){ return std::rand()%2; }).name("cond_2");
tf::Task cond_3 = taskflow.emplace([](){ return std::rand()%2; }).name("cond_3");
A.precede(B, F);
B.precede(C);
C.precede(D);
D.precede(cond_1);
E.precede(K);
F.precede(cond_2);
H.precede(I);
I.precede(cond_3);
L.precede(M);
cond_1.precede(B, E); // return 0 to 'B' or 1 to 'E'
cond_2.precede(G, H); // return 0 to 'G' or 1 to 'H'
cond_3.precede(cond_3, L); // return 0 to 'cond_3' or 1 to 'L'
taskflow.dump(std::cout);
}
常见流程图设计准则
为了了解执行者如何安排条件任务,定义了两种依赖类型,强依赖和弱依赖。强大的依赖性是从非条件任务到另一个任务的前一个环节。弱依赖项是从条件任务到另一个任务的前一个链接。任务的从属数量是强依赖和弱依赖的总和。
- 单无源节点,减少竞争
- 无源节点不是条件节点
- 一个节点最好不要混合依赖(既有强依赖,又有弱依赖/条件依赖)
实现控制流图
if else 型
#include <taskflow/taskflow.hpp>
int main() {
tf::Taskflow taskflow;
tf::Executor executor;
int i;
// create three condition tasks for nested control flow
auto initi = taskflow.emplace([&](){ i=3; });
auto cond1 = taskflow.emplace([&](){ return i>1 ? 1 : 0; });
auto cond2 = taskflow.emplace([&](){ return i>2 ? 1 : 0; });
auto cond3 = taskflow.emplace([&](){ return i>3 ? 1 : 0; });
auto equl1 = taskflow.emplace([&](){ std::cout << "i=1\n"; });
auto equl2 = taskflow.emplace([&](){ std::cout << "i=2\n"; });
auto equl3 = taskflow.emplace([&](){ std::cout << "i=3\n"; });
auto grtr3 = taskflow.emplace([&](){ std::cout << "i>3\n"; });
initi.precede(cond1);
cond1.precede(equl1, cond2); // goes to cond2 if i>1
cond2.precede(equl2, cond3); // goes to cond3 if i>2
cond3.precede(equl3, grtr3); // goes to grtr3 if i>3
taskflow.dump(std::cout);
}
switch 型
#include <taskflow/taskflow.hpp>
int main() {
tf::Taskflow taskflow;
tf::Executor executor;
auto [source, swcond, case1, case2, case3, target] = taskflow.emplace(
[](){ std::cout << "source\n"; },
[](){ std::cout << "switch\n"; return rand()%3; },
[](){ std::cout << "case 1\n"; return 0; },
[](){ std::cout << "case 2\n"; return 0; },
[](){ std::cout << "case 3\n"; return 0; },
[](){ std::cout << "target\n"; }
);
source.precede(swcond);
swcond.precede(case1, case2, case3);
target.succeed(case1, case2, case3);
taskflow.dump(std::cout);
}
注意: 在switch型中,case节点必须是条件节点(也就是说必须要有返回值),因为如果是普通的静态节点,那么taget节点强依赖于case1、case2、case3 三个节点,但是这三个节点只会有一个被执行,这就导致targer永远无法完成前置依赖,导致永久等待。
实现do while 循环式
#include <taskflow/taskflow.hpp>
int main() {
tf::Taskflow taskflow;
tf::Executor executor;
int i;
auto [init, body, cond, done] = taskflow.emplace(
[&](){ std::cout << "i=0\n"; i=0; },
[&](){ std::cout << "i++ => i="; i++; },
[&](){ std::cout << i << '\n'; return i<5 ? 0 : 1; },
[&](){ std::cout << "done\n"; }
);
init.precede(body);
body.precede(cond);
cond.precede(body, done);
taskflow.dump(std::cout);
}
while Loop 型
#include <taskflow/taskflow.hpp>
int main() {
tf::Taskflow taskflow;
tf::Executor executor;
int i;
auto [init, cond, body, back, done] = taskflow.emplace(
[&](){ std::cout << "i=0\n"; i=0; },
[&](){ std::cout << "while i<5\n"; return i < 5 ? 0 : 1; },
[&](){ std::cout << "i++=" << i++ << '\n'; },
[&](){ std::cout << "back\n"; return 0; },
[&](){ std::cout << "done\n"; }
);
init.precede(cond);
cond.precede(body, done);
body.precede(back);
back.precede(cond);
taskflow.dump(std::cout);
}
注意这里的细节,i++节点后,不能立马指向 while < 5 的条件节点,因为i++ 是普通节点,对cond做强制依赖,会导致死锁,如下图所示:
在上面的任务流程图中,调度器从init开始,然后减少循环条件任务的强依赖性,而i<5。在此之后,仍然存在一个强大的依赖性,即由循环主体任务i++引入。然而,在循环条件任务返回0之前,任务i++不会被执行,导致死锁
多条件任务
多条件任务是条件任务的广义版本。在某些情况下,应用程序需要从父任务跳转到多个分支。这可以通过创建一个多条件任务来完成,该任务允许任务选择一个或多个后续任务来执行。与条件任务类似,多条件任务返回一个整数索引向量,该向量指示多条件任务完成后要执行的继任者。该指数是根据多条件任务之前的继任者顺序定义的。
#include <taskflow/taskflow.hpp>
int main() {
tf::Taskflow taskflow;
tf::Executor executor;
auto A = taskflow.emplace([]() -> tf::SmallVector<int> {
std::cout << "A\n";
return {0, 2}; // 表示0 和 2 都可以激活
}).name("A");
auto B = taskflow.emplace([&](){ std::cout << "B\n"; }).name("B");
auto C = taskflow.emplace([&](){ std::cout << "C\n"; }).name("C");
auto D = taskflow.emplace([&](){ std::cout << "D\n"; }).name("D");
A.precede(B, C, D);
executor.run(taskflow).wait();
taskflow.dump(std::cout);
}
在这里,B和D均消除了前置依赖,所以都会往下执行。同样,后继索引的正确性,需要由用户自己保证,对于错误的索引,Taskflow会直接略过。