taskflow / taskflow

A General-purpose Parallel and Heterogeneous Task Programming System

Home Page:https://taskflow.github.io

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Functor usage example? Intended behavior?

wilderfield opened this issue · comments

I was trying to create a basic example that uses a stateful functor.

The documentation says functors or class objects with operator() overloaded can be used as a task.

However, when I tried this out, it seems that the class object is copied.

The state is lost.

Are lambda captures the only way to share state across tasks?

What I really want to do is create a data flow pipeline.

TaskA reads input buffer A, fills output buffer A.
TaskB reads output buffer A, fills output buffer B.
TaskC reads output buffer B, fills output buffer C.

^ Sequential for now, but some parallel to come.

#include <taskflow/taskflow.hpp>  // the only include you need

int main(){

  tf::Executor executor;
  tf::Taskflow taskflow("simple");

  // A Functor
  class increment
  {
  public:
    int num;
    increment(int n) : num(n) {  }

    void operator () (void) {
        num += 1;
	std::cout << num << std::endl;
	std::cout << &num << std::endl;
    }
  };

  increment inc(1);


  auto [A, B] = taskflow.emplace(
    inc,
    inc
  );

  A.precede(B);  // A runs before B

  executor.run(taskflow).wait();

  std::cout << inc.num << std::endl;

  taskflow.dump(std::cout);

  return 0;
}

This prints:

2
0x55963e39a440
2
0x55963e39a348
1
digraph Taskflow {
subgraph cluster_p0x7ffdd552b700 {
label="Taskflow: simple";
p0x55963e39a290[label="p0x55963e39a290" ];
p0x55963e39a388[label="p0x55963e39a388" ];
p0x55963e39a388 -> p0x55963e39a290;
}
}

I got some help from ChatGPT, and this is working how I wanted it to.

I can take a class method and bind it to a std::function object.

Then it works as I want it to.

However, if you have suggestions on cleanest way to build dataflow graph, it would be appreciated.

#include <taskflow/taskflow.hpp>  // the only include you need

int main(){

  tf::Executor executor;
  tf::Taskflow taskflow("simple");

  class increment
  {
  public:
    int num;
    increment(int n) : num(n) {  }

    void add(void) {
        num += 1;
	std::cout << num << std::endl;
	std::cout << &num << std::endl;
    }
  };

  increment inc(1);

  std::function<void(void)> func = std::bind(&increment::add, &inc);


  auto [A, B] = taskflow.emplace(
    func,
    func
  );

  A.precede(B);  // A runs before B

  executor.run(taskflow).wait();

  std::cout << inc.num << std::endl;

  taskflow.dump(std::cout);

  return 0;
}

@wilderfield yes, under the hood, Taskflow use std::function to store each task. Per std::function definition, it will store the target with copy semantics. So you will need to either use std::bind or create another lambda to explicitly call the inc with reference semantics

auto [A, B] = taskflow.emplace(
  [&](){ inc(); },
  [&](){ inc(); }
)

Thank you!