Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Async future with a callback. C++11

I have a list of futures. The problem is that I have a lot of files and I need to make some long operation after every file will be created. That's why I want to make a callback after each "file save".

E.g.,

  (new thread; saveFile 1.txt -> new thread; do a long operation after the file has been created)
  (new thread; saveFile 2.pdf -> new thread; do a long operation after the file has been created).

I need to do everything in a separate thread. The saving of the file is critical, the second task can't be run before the file will have been created. How can I do it? I have the following code:

 void save_file() {
     // preparing data...
     saving a file
   } 

   std::vector<std::future<void>> saveFileTasks;
   for (int n = 0; n < p.size(); ++n)
   {
      saveFileTasks.push_back(std::async(std::bind(&saveFile, filename)));
   }

   for (auto &e : saveFileTasks) {
      e.get();
   }

How can I make a callback in C++11 with future/promise? I am not allowed to use boost in my project.

I'm really confused, there are so much complicated examples for a very simple task. A lot of examples can't be compilable, e.g., promise.set_wait_callback doesn't exist in C++11 but many functions have been migrated to C++11. I can do it really easy if I use Python or Clojure. How can I do it with C++?

like image 845
user565447 Avatar asked Feb 22 '17 14:02

user565447


People also ask

Can a callback function be async?

The function that takes another function as an argument is called a higher-order function. According to this definition, any function can become a callback function if it is passed as an argument. Callbacks are not asynchronous by nature, but can be used for asynchronous purposes.

How does async callback work?

For JavaScript to know when an asynchronous operation has a result (a result being either returned data or an error that occurred during the operation), it points to a function that will be executed once that result is ready. This function is what we call a “callback function”.

Are callbacks asynchronous C++?

The callback function itself is actually called within the definition of the principal function. The basic callback function in C++ does not guarantee asynchronous behavior in a program.

What does STD async return?

std::async returns a std::future<T>, that stores the value returned by function object executed by std::async(). Arguments expected by function can be passed to std::async() as arguments after the function pointer argument.

What is the asynchronous callback function scheme in C++?

In the asynchronous callback function scheme, the result of the principal function should be obtained for the program before the result of the callback function is obtained. It is possible to do this in C++; however, C++ has a library called future to guarantee the behavior of the asynchronous callback function scheme.

What is a callback function?

A callback function is a function, which is an argument, not a parameter, in another function. The other function can be called the principal function. So two functions are involved: the principal function and the callback function itself.

What are the advantages of asynchronous callback paradigm?

The asynchronous callback paradigm significantly eases multithreaded application development by placing the callback function pointer and callback data onto the thread of control that you specify. Exposing a callback interface for a single module or an entire subsystem is extremely easy.

How does a synchronous callback work?

If the callback is synchronous, the callback is executed on the caller's thread of control. If you put a break point inside the callback, the stack frame will show the publisher function call and the publisher callback all synchronously invoked.


2 Answers

Unfortunately there is no .then continuation in the current version of std::future - it is proposed alongside similar utilities for a future standard of C++.

If you cannot use boost, you can build your own continuation with function composition:

string save_file(string data)      { /* ... */ return filename; } // step 1
void do_operation(string filename) { /* ... */ }                  // step 2

// ... 

std::vector<std::future<void>> fileTasks;
for(const auto& data : /* ... */)
{
    fileTasks.emplace_back(std::async(std::launch::async, 
        [data]{ do_operation(save_file(data)); });
}

Note that std::async([data]{ do_operation(save_file(data)); } will execute both functions in the same thread. If you want each function to be executed in a separate thread you can call async multiple times:

std::async(std::launch::async, [data]
{
    auto result = save_file(data);
    std::async(std::launch::async, [r = std::move(result)]
    {
        do_operation(std::move(r));
    });
});

With either boost::future or a future version of the standard, you could simply say:

std::async(std::launch::async, [data]{ save_file(data); })
    .then([](string filename){ do_operation(filename); );
like image 63
Vittorio Romeo Avatar answered Oct 15 '22 08:10

Vittorio Romeo


In the future, future will have a .then operator that lets you chain tasks.

Lacking it we can write it.

// complete named operator library in about a dozen lines of code:
namespace named_operator {
  template<class D>struct make_operator{ constexpr make_operator() {}; };

  template<class T, class O> struct half_apply { T&& lhs; };

  template<class Lhs, class Op>
  half_apply<Lhs, Op> operator*( Lhs&& lhs, make_operator<Op> ) {
    return {std::forward<Lhs>(lhs)};
  }
  template<class Lhs, class Op, class Rhs>
  decltype(auto) operator*( half_apply<Lhs, Op>&& lhs, Rhs&& rhs )
  {
    return named_invoke( std::forward<Lhs>(lhs.lhs), Op{}, std::forward<Rhs>(rhs) );
  }
}

// create a named operator then:
namespace then_ns {
  static const struct then_t:named_operator::make_operator<then_t> {} then{};

  namespace details {
    template<size_t...Is, class Tup, class F>
    auto invoke_helper( std::index_sequence<Is...>, Tup&& tup, F&& f )
    ->decltype(std::forward<F>(f)( std::get<Is>(std::forward<Tup>(tup))... ))
    {
      return std::forward<F>(f)( std::get<Is>(std::forward<Tup>(tup))... );
    }
  }

  // first overload of A *then* B handles tuple and tuple-like return values:
  template<class Tup, class F>
  auto named_invoke( Tup&& tup, then_t, F&& f )
  -> decltype( details::invoke_helper( std::make_index_sequence< std::tuple_size<std::decay_t<Tup>>{} >{}, std::forward<Tup>(tup), std::forward<F>(f) ) )
  {
    return details::invoke_helper( std::make_index_sequence< std::tuple_size<std::decay_t<Tup>>{} >{}, std::forward<Tup>(tup), std::forward<F>(f) );
  }

  // second overload of A *then* B
  // only applies if above does not:
  template<class T, class F>
  auto named_invoke( T&& t, then_t, F&& f, ... )
  -> std::result_of_t< F(T) >
  {
    return std::forward<F>(f)(std::forward<T>(t));
  }
  // *then* with a future; unpack the future
  // into a call to f within an async:
  template<class X, class F>
  auto named_invoke( std::future<X> x, then_t, F&& f )
  -> std::future< std::decay_t<decltype( std::move(x).get() *then* std::declval<F>() )> >
  {
    return std::async( std::launch::async,
      [x = std::move(x), f = std::forward<F>(f)]() mutable {
        return std::move(x).get() *then* std::move(f);
      }
    );
  }
  // void future, don't try to pass void to f:
  template<class F>
  auto named_invoke( std::future<void> x, then_t, F&& f )
  -> std::future< std::decay_t<decltype( std::declval<F>()() )> >
  {
    return std::async( std::launch::async,
      [x = std::move(x), f = std::forward<F>(f)]() mutable {
        std::move(x).get();
        return std::move(f)();
      }
    );
  }
}
using then_ns::then;

see, that wasn't that hard.

a *then* f, if a is a tuple (or pair or array), will invoke f with the contents of a.

If a isn't tuple-like, or f doesn't accept the contents of a that way, it invokes f with a.

If a is a future, it instead creates a new async future that consumes a.get() using *then*.

Live example.

Suppose you want to increase an atomic int when the file is saved:

std::vector<std::future<void>> saveFileTasks;
for (int n = 0; n < p.size(); ++n)
{
  saveFileTasks.push_back(
    std::async(std::launch::async, [filename]{
      saveFile(filename);
    })
  );
}
std::atomic<int> count;
for (auto &e : saveFileTasks) {
  e = std::move(e) *then* [&count]{
    ++count;
  });
}

Naturally this can all be done without the named operator *then* style syntax, but what is the fun of that?

If the first async returns a tuple, the second one can either take it as a tuple or as unpacked "flat" arguments.

like image 33
Yakk - Adam Nevraumont Avatar answered Oct 15 '22 08:10

Yakk - Adam Nevraumont