Как использовать boost :: packaged_task, параметры функции и boost :: asio :: io_service?

Прежде всего, я хочу извиниться за объемный пост. Я хотел быть максимально подробным.

Я застрял в этой проблеме уже несколько дней, и на удивление мало информации о правильном использовании boost::packaged_task в функции, которая имеет входные параметры.

Системная информация

  • C++03
  • Повышение 1.54.0
  • CMake 2.8.9

Первоначальное требование

  1. У меня есть установка, состоящая из клиента (ов), сервера и устройства (а).
  2. A client interacts with a device by sending requests to the server.
    • These requests are examined and routed to the appropriate device.
    • Запросы обрабатываются асинхронно и иногда по разным причинам помещаются в очередь через boost::asio::io_service::strand.
  3. Requests are placed into a queue, local to the device itself.
    • When the request has been acknowledged (not necessarily completed), it is assigned an ID, and returned to the client.

Пакетная задача

После просмотра boost :: Futures мы решили, что boost :: packaged_task сделает именно то, что нам нужно. Однако, похоже, есть ошибка в реализации пакетной задачи.

Похоже, что у packaged_task есть несколько разных шаблонов на выбор:

  1. packaged_task<R>
  2. packaged_task<R()>
  3. packaged_task<R(ArgTypes)>
  4. Другие, которые мне могут не хватать.

Чтобы убедиться, что я правильно использую функцию, я начал с простого; используя простой пример на странице boost :: futures в качестве отправной точки. Оттуда я создал четыре простых функции:

  • int return, без параметров.
  • int return с параметрами.
  • std::string возврат, без параметров.
  • std::string возврат с параметрами.

Тестовые функции

std::string ans("forty two");

int int_no_params()
{
    return 42;
}

int int_with_params(int param)
{
    return param;
}

std::string string_no_params()
{
    return std::string("forty two");
}

std::string string_with_params(std::string & param) // Have tried both with and without '&'
{
    return param;
}

ПРИМЕР 1:

int function(void)

    //! Compiles and produces correct result.  
    {
        boost::packaged_task<int()> example(int_no_params);
        boost::future<int> f = example.get_future();
        boost::thread task(boost::move(example));
        int answer = f.get();
        std::cout << "Answer to life and whatnot, in English: " << answer << std::endl;
        task.join();
    }

ПРИМЕР 2:

std::string function(void)

    //! Compiles and produces correct result.
    {
        boost::packaged_task<std::string()> example(string_no_params);
        boost::future<std::string> f = example.get_future();
        boost::thread task(boost::move(example));
        std::string answer = f.get();
        std::cout << "string_no_params: " << answer << std::endl;
        task.join();
    }

ПРИМЕР 3:

std::string(std::string& param) Нет резьбы

//! Doesn't compile.
//! error: variable ‘boost::packaged_task<std::basic_string<char>(std::basic_string<char>&)> example’ has initializer but incomplete type

{
    boost::packaged_task<std::string(std::string&)> example(string_with_params);
    boost::future<std::string> f = example.get_future();
    example(ans);
    std::string answer = f.get();
    std::cout << "string_with_params: " << answer << std::endl;
}

ПРИМЕР 4:

с использованием boost :: threading

//! Doesn't compile.
//! error: variable ‘boost::packaged_task<std::basic_string<char>(std::basic_string<char>&)> example’ has initializer but incomplete type
{
    boost::packaged_task<std::string(std::string&)> example(string_with_params);
    boost::future<std::string> f = example.get_future();
    boost::thread task(boost::move(example), ans);
    std::string answer = f.get();
    std::cout << "string_with_params: " << answer << std::endl;
    task.join();
}

ПРИМЕР 5:

Использование расширенных инициализаторов в объявлении packaged_task

//! Doesn't compile in C++03, C++11 only.
//! error: extended initializer lists only available with -std=c++11 or -std=gnu++11 [-Werror]
{
    boost::packaged_task<std::string(std::string&)> example
    { boost::bind(&string_with_params, ans) };
    boost::future<std::string> f = example.get_future();
    boost::thread task(boost::move(example), ans);
    std::string answer = f.get();
    std::cout << "string_with_params: " << answer << std::endl;
    task.join();
}

ПРИМЕР 6:

Потоковый, с использованием shared_ptr

Следующее использование typedef boost::packaged_task<std::string(std::string&)> task_t;

Поскольку упакованные задачи не могут быть скопированы, привязка shared_ptr<T>::operator() к task была предложенным решением, найденным здесь.

// error: invalid use of incomplete type ‘class boost::packaged_task<std::basic_string<char>(std::basic_string<char>&)>’
// error: incomplete type ‘task_t {aka boost::packaged_task<std::basic_string<char>(std::basic_string<char>&)>}’ used in nested name specifier
// boost/thread/future.hpp:1320:11: error: declaration of ‘class boost::packaged_task<std::basic_string<char>(std::basic_string<char>&)>’
{
    boost::shared_ptr<task_t> example = boost::make_shared<task_t>(boost::bind(&string_with_params, ans));
    boost::future<std::string> f = example->get_future();
    boost::thread task(boost::bind(&task_t::operator(), example));
    std::string answer = f.get();
    std::cout << "string_with_params: " << answer << std::endl;
    task.join();
}

ПРИМЕР 7:

Использование boost::asio::io_service и boost::bind

// ошибка: недопустимое использование неполного типа 'class boost :: packaged_task (std :: basic_string &)>' // ошибка: неполный тип 'task_t {aka boost :: packaged_task (std :: basic_string &)>}' используется во вложенном имени спецификатор // boost / thread / future.hpp: 1320: 11: error: объявление класса boost :: packaged_task (std :: basic_string &)> '

{
    boost::asio::io_service io_service;
    boost::thread_group threads;
    boost::asio::io_service::work work(io_service);

    for (int i = 0; i < 3; ++i)
    {
        threads.create_thread(boost::bind(&boost::asio::io_service::run,
            &io_service));
    }

    boost::shared_ptr<task_t> example = boost::make_shared<task_t>(boost::bind(&string_with_params, ans));
    boost::future<std::string> f = example->get_future();
    io_service.post(boost::bind(&task_t::operator(), example));
    std::string answer = f.get();
    std::cout << "string_with_params: " << answer << std::endl;
    threads.join_all();
}

Что-то я здесь делаю ужасно неправильно? Мне кажется, что я исчерпывающе протестировал это и не продвинулся вперед. Я пробовал любую другую комбинацию привязок, потоков и задач, чтобы заставить это работать, но этого просто не происходит. Я ценю любую помощь, которую вы оказываете.

В заключение:

У меня есть рабочее решение, использующее фьючерсы и обещания, и, используя частную функцию для публикации в моем потоке, я возвращаю действительное будущее. Эта проблема не обязательно связана с ошибкой пользователя.

Спасибо за чтение.


person JohanBjorn    schedule 24.10.2013    source источник


Ответы (1)


Хотя я не могу найти ограничение, явно указанное в документации, История изменений отмечает, что возможность предоставлять типы аргументов в packaged_task Boost.Thread предназначена для соответствия C ++ 11:

Соответствие C ++ 11: добавить ArgTypes в packaged_task шаблон. Предоставляется, когда определено BOOST_THREAD_PROVIDES_SIGNATURE_PACKAGED_TASK (значение по умолчанию из Boost 1.55).

В соответствующем заявке говорится, что, когда вариативные шаблоны недоступны, только подпись R() будет предоставляться.

Поскольку в C ++ 03 отсутствуют вариативные шаблоны, пример 3-7 завершится ошибкой. Более того, существует несоответствие типов с примерами 6 и 7. Хотя task_t определяет тип своей функции как std::string(std::string&), первый и единственный аргумент привязывается к функтору во время boost::bind(). Поскольку результирующий функтор больше не ожидает аргументов, тип функции, предоставленный для packaged_task, должен быть std::string().

Хотя packaged_task не поддерживает аргументы в C ++ 03, одним из промежуточных решений является создание типа функтора, который охватывает нижний уровень _ 12_. Без поддержки вариативных шаблонов и безупречной пересылки будет много шаблонного кода для operator() перегрузок. Тем не менее, вот базовый пример функтора, который игнорирует обработку исключений между promise и future:

/// @brief basic_task to support function types with arguments.  This
///        provides a minimal feature workaround to Boost.Thread's
///        packaged_task not supporting argument types for C++03.
template <typename Fn>
class basic_task
{
public:
  // @brief The type the future will return.
  typedef typename boost::function_types::result_type<Fn>::type result_type;

  typedef boost::promise<result_type> promise_type;

  /// @brief Constructor.
  template <typename F> 
  explicit basic_task(const F& f)
    : fn_(f),
      promise_(boost::make_shared<promise_type>())
  {}

  // Overload operator() functions.

  void operator()()
  {
    promise_->set_value(fn_());
  }

  template <typename A1>
  void operator()(const A1& a1)
  {
    promise_->set_value(fn_(a1));
  }

  template <typename A1>
  void operator()(A1& a1)
  {
    promise_->set_value(fn_(a1));
  }

  /// @brief Get a future for this task' promise.
  boost::unique_future<result_type>
  get_future()
  {
    return promise_->get_future();
  }

private:
  boost::function<Fn> fn_;
  boost::shared_ptr<promise_type> promise_;
};

Полная серия примеров:

#include <iostream>
#include <string>

#define BOOST_THREAD_PROVIDES_SIGNATURE_PACKAGED_TASK

#include <boost/asio.hpp>
#include <boost/function_types/result_type.hpp>
#include <boost/make_shared.hpp>
#include <boost/thread.hpp>

/// @brief basic_task to support function types with arguments.  This
///        provides a minimal feature workaround to Boost.Thread's
///        packaged_task not supporting argument types for C++03.
template <typename Fn>
class basic_task
{
public:
  // @brief The type the future will return.
  typedef typename boost::function_types::result_type<Fn>::type result_type;

  typedef boost::promise<result_type> promise_type;

  /// @brief Constructor.
  template <typename F> 
  explicit basic_task(const F& f)
    : fn_(f),
      promise_(boost::make_shared<promise_type>())
  {}

  // Overload operator() functions.

  void operator()()
  {
    promise_->set_value(fn_());
  }

  template <typename A1>
  void operator()(const A1& a1)
  {
    promise_->set_value(fn_(a1));
  }

  template <typename A1>
  void operator()(A1& a1)
  {
    promise_->set_value(fn_(a1));
  }

  /// @brief Get a future for this task' promise.
  boost::unique_future<result_type>
  get_future()
  {
    return promise_->get_future();
  }

private:
  boost::function<Fn> fn_;
  boost::shared_ptr<promise_type> promise_;
};

std::string ans("forty two");

int int_no_params()
{
  return 42;
}

int int_with_params(int param)
{
  return param;
}

std::string string_no_params()
{
  return std::string("forty two");
}

std::string string_with_params(std::string & param)
{
  return param;
}

int main()
{
  // example 1
  {
    boost::packaged_task<int()> example(&int_no_params);
    boost::unique_future<int> f = example.get_future();
    boost::thread task(boost::move(example));
    int answer = f.get();
    std::cout << "Answer to life and whatnot, in English: "
              << answer << std::endl;
    task.join();
  }

  // example 2
  {
    boost::packaged_task<std::string()> example(&string_no_params);
    boost::unique_future<std::string> f = example.get_future();
    boost::thread task(boost::move(example));
    std::string answer = f.get();
    std::cout << "string_no_params: " << answer << std::endl;
    task.join();
  }

  // example 3
  {
    basic_task<std::string(std::string&)> example(&string_with_params);
    boost::unique_future<std::string> f = example.get_future();
    example(ans);
    std::string answer = f.get();
    std::cout << "string_with_params: " << answer << std::endl;
  }

  // example 4
  {
    basic_task<std::string(std::string&)> example(&string_with_params);
    boost::unique_future<std::string> f = example.get_future();
    boost::thread task(boost::move(example), ans);
    std::string answer = f.get();
    std::cout << "string_with_params: " << answer << std::endl;
    task.join();
  }

  // example 5
  {
    basic_task<std::string(std::string&)>
        example(boost::bind(&string_with_params, ans));
    boost::unique_future<std::string> f = example.get_future();
    boost::thread task(boost::move(example), ans);
    std::string answer = f.get();
    std::cout << "string_with_params: " << answer << std::endl;
    task.join();
  }

  // example 6
  {
    typedef boost::packaged_task<std::string()> task_t;
    boost::shared_ptr<task_t> example =
        boost::make_shared<task_t>(boost::bind(&string_with_params, ans));
    boost::unique_future<std::string> f = example->get_future();
    boost::thread task(boost::bind(&task_t::operator(), example));
    std::string answer = f.get();
    std::cout << "string_with_params: " << answer << std::endl;
    task.join();
  }

  // example 7
  {
    boost::asio::io_service io_service;
    boost::thread_group threads;
    boost::asio::io_service::work work(io_service);

    for (int i = 0; i < 3; ++i)
      threads.create_thread(
          boost::bind(&boost::asio::io_service::run, &io_service));

    typedef boost::packaged_task<std::string()> task_t;
    boost::shared_ptr<task_t> example =
        boost::make_shared<task_t>(boost::bind(&string_with_params, ans));
    boost::unique_future<std::string> f = example->get_future();
    io_service.post(boost::bind(&task_t::operator(), example));
    std::string answer = f.get();
    std::cout << "string_with_params: " << answer << std::endl;
    io_service.stop();
    threads.join_all();
  }
}

И полученный результат:

Answer to life and whatnot, in English: 42
string_no_params: forty two
string_with_params: forty two
string_with_params: forty two
string_with_params: forty two
string_with_params: forty two
string_with_params: forty two
person Tanner Sansbury    schedule 28.10.2013
comment
Спасибо, что нашли время ответить. После головных болей, связанных со всей библиотекой фьючерсов, мы приняли решение пойти другим путем. Было несколько ситуаций, в которых использовались упакованные задачи и фьючерсы, в частности .then (...), что приводило к тупиковой ситуации в нашем приложении. - person JohanBjorn; 02.11.2013