Используйте функцию-член в std::packaged_task

То, что я хочу сделать, должно быть довольно легко, но я не понимаю...

Все, что я хочу сделать, - это запустить функцию-член класса в фоновом режиме в определенный момент времени. Результат этой функции также должен быть доступен "извне". Поэтому я хочу подготовить задачу в конструкторе (установив будущую переменную,...) и запустить ее на более позднем этапе.

Я пытался скомбинировать std::(​​packaged_task|async|future), но я не получил его на работу.

Этот фрагмент не будет компилироваться, но я думаю, что он показывает, что я хочу сделать:

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(); }
};

Заранее спасибо!

2 ответа

Решение

Просто используйте std::bind():

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

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

Более того, здесь вы делаете не то:

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

Так как вы хотите позвонить std::async() функция, чтобы не объявлять объект (несуществующего) типа std::async(), В качестве первого шага измените это на:

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

Однако обратите внимание, что этого будет недостаточно для асинхронного выполнения задачи: из-за странного поведения std::async() когда возвращенное будущее отбрасывается, ваша задача всегда будет выполняться так, как если бы вы запустили ее синхронно - деструктор возвращенного будущего объекта будет блокироваться до завершения операции. (*)

Чтобы решить эту последнюю проблему, вы можете держать возвращенное будущее в своем result переменная-член (вместо присвоения result будущее вернулось std::packaged_task::get_future() при строительстве):

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

(*) Я думаю, что MSVC игнорирует эту спецификацию и фактически выполняет задачу асинхронно. Так что если вы работаете с VS2012, вы можете не страдать от этой проблемы.

РЕДАКТИРОВАТЬ:

Как правильно отметил Преториан в своем ответе, вышеизложенное все равно будет проблематичным, поскольку копия packaged_task будет предпринята попытка в какой-то момент в рамках реализации async(), Чтобы обойти эту проблему, вы заверните task объект в ссылочной обертке с помощью std::ref(),

do_something() является функцией-членом, что означает, что она принимает неявное this указатель в качестве первого аргумента. Вам нужно будет bind this указатель или создать лямду, которая вызывает do_something,

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

или же

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

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

std::async это шаблон функции, а не тип. Таким образом, очевидное изменение

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

Но это вызывает еще одну ошибку, потому что где-то в кишечнике этого вызывают копию task попытка, но std::packaged_task имеет конструктор удаленных копий. Вы можете исправить это, используя std::ref, что позволит избежать копирования.

std::async(std::launch::async, std::ref(task));
Другие вопросы по тегам