Taskflow:条件任务(Conditional Tasking)

文章详细介绍了如何使用C++的Taskflow库创建各种条件任务,如if-else、switch、while和多条件任务,以及如何定义它们之间的依赖关系,包括强依赖和弱依赖。作者展示了如何通过这些结构实现复杂任务逻辑和控制流程。
摘要由CSDN通过智能技术生成

基本使用

条件任务评估一组指令,并返回要执行的下一个后续任务的整数索引。
该索引是根据其继任者结构的顺序定义的,首先,创建一个最简单的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);
}

在这里插入图片描述

常见流程图设计准则

为了了解执行者如何安排条件任务,定义了两种依赖类型,强依赖和弱依赖。强大的依赖性是从非条件任务到另一个任务的前一个环节。弱依赖项是从条件任务到另一个任务的前一个链接。任务的从属数量是强依赖和弱依赖的总和。

  1. 单无源节点,减少竞争
  2. 无源节点不是条件节点
  3. 一个节点最好不要混合依赖(既有强依赖,又有弱依赖/条件依赖)

在这里插入图片描述

实现控制流图

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会直接略过。

  • 17
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值