Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use member function in std::packaged_task

What I want to do should be quite easy, but I don't get it...

All I want to do is to start a member function of a class in background at some certain point in time. The result of that function should also be "externally" available. So I want to prepare the task in the constructor (setting the future variable, ... ) and start it at some later point.

I tried to combine std::(packaged_task|async|future) but I didn't get it to work.

This snippet will not compile, but I think it shows what I want to do:

class foo {
private:
  // This function shall run in background as a thread
  // when it gets triggered to start at some certain point
  bool do_something() { return true; }

  std::packaged_task<bool()> task;
  std::future<bool> result;

public:
  foo() : 
    task(do_something), // yes, that's wrong, but how to do it right?
    result(task.get_future()) 
  {
    // do some initialization stuff
    .....
  }
  ~foo() {}

  void start() {
    // Start Task as asynchron thread
    std::async as(std::launch::async, task); // Also doesn't work...
  }

  // This function should return the result of do_something
  bool get_result() { return result.get(); }
};

Thanks in advance!

like image 733
rralf Avatar asked Jun 24 '13 22:06

rralf


2 Answers

Just use std::bind():

#include <functional> // For std::bind()

foo() :
    task(std::bind(&foo::do_something, this)),
//       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    result(task.get_future()) 
{
    //  ...
}

Moreover, you are doing the wrong thing here:

std::async as(std::launch::async, task)
//         ^^
//         Trying to declare a variable?

Since what you want is to call the std::async() function, not to declare an object of a (non-existing) type std::async(). So as a first step, change this into:

std::async(std::launch::async, task)

Notice, however, that this will not be enough to get the task running asynchronously: because of the odd behavior of std::async() when the returned future is discarded, your task will always be executed as if you started it synchronously - the destructor of the returned future object will block until the completion of the operation. (*)

To solve this last problem, you can hold the returned future in your result member variable (rather than assigning to result the future returned by std::packaged_task::get_future() upon construction):

    result = std::async(std::launch::async, task);
//  ^^^^^^^^

(*) I think that MSVC ignores this specification and actually executes the task asynchronously. So if you are working with VS2012, you may not suffer from this problem.

EDIT:

As correctly mentioned by Praetorian in his answer, the above would still be problematic, since a copy of the packaged_task would be attempted at some point within the implementation of async(). To work around this problem, you wrap your task object in a reference wrapper by using std::ref().

like image 81
Andy Prowl Avatar answered Sep 19 '22 22:09

Andy Prowl


do_something() is a member function, which means it takes an implicit this pointer as the first argument. You'll need to bind the this pointer, or create a lamda that invokes do_something.

foo() : 
  task(std::bind(&foo::do_something, this)),
  result(task.get_future()) 
{}

or

foo() : 
  task([this]{ return do_something(); }),
  result(task.get_future()) 
{}

std::async as(std::launch::async, task);

std::async is a function template, not a type. So the obvious change is

std::async(std::launch::async, task);

But that causes yet another error because somewhere within the guts of that call a copy of task is attempted, but std::packaged_task has a deleted copy constructor. You can fix that by using std::ref, which will avoid the copy.

std::async(std::launch::async, std::ref(task));
like image 25
Praetorian Avatar answered Sep 19 '22 22:09

Praetorian