В настоящее время мы очень сильно используем асинхронные значения.
Предположим, что у меня есть функция, которая делает что-то вроде этого:
int do_something(const boost::posix_time::time_duration& sleep_time)
{
BOOST_MESSAGE("Sleeping a bit");
boost::this_thread::sleep(sleep_time);
BOOST_MESSAGE("Finished taking a nap");
return 42;
}
В какой-то момент в коде мы создаем задачу, которая создает будущее для такого значения int, которое будет установлено packaged_task — вот так (worker_queue в этом примере является boost :: asio :: io_service):
boost::unique_future<int> createAsynchronousValue(const boost::posix_time::seconds& sleep)
{
boost::shared_ptr< boost::packaged_task<int> > task(
new boost::packaged_task<int>(boost::bind(do_something, sleep)));
boost::unique_future<int> ret = task->get_future();
// Trigger execution
working_queue.post(boost::bind(&boost::packaged_task<int>::operator (), task));
return boost::move(ret);
}
В другом месте кода я хочу обернуть эту функцию, чтобы вернуть объект более высокого уровня, который также должен стать будущим. Мне нужна функция преобразования, которая принимает первое значение и преобразует его в другое значение (в нашем реальном коде у нас есть несколько уровней и асинхронный RPC, который возвращает фьючерсы на ответы — эти ответы должны быть преобразованы во фьючерсы в реальные объекты, POD или даже в void будущее, чтобы иметь возможность ждать или ловить исключения). Так что это функция преобразования в этом примере:
float converter(boost::shared_future<int> value)
{
BOOST_MESSAGE("Converting value " << value.get());
return 1.0f * value.get();
}
Затем я подумал о создании ленивого будущего, как описано в документации Boost, чтобы сделать это преобразование только при желании:
void invoke_lazy_task(boost::packaged_task<float>& task)
{
try
{
task();
}
catch(boost::task_already_started&)
{}
}
И тогда у меня есть функция (может быть API более высокого уровня) для создания обернутого будущего:
boost::unique_future<float> createWrappedFuture(const boost::posix_time::seconds& sleep)
{
boost::shared_future<int> int_future(createAsynchronousValue(sleep));
BOOST_MESSAGE("Creating converter task");
boost::packaged_task<float> wrapper(boost::bind(converter, int_future));
BOOST_MESSAGE("Setting wait callback");
wrapper.set_wait_callback(invoke_lazy_task);
BOOST_MESSAGE("Creating future to converter task");
boost::unique_future<float> future = wrapper.get_future();
BOOST_MESSAGE("Returning the future");
return boost::move(future);
}
В конце я хочу использовать его следующим образом:
{
boost::unique_future<float> future = createWrappedFuture(boost::posix_time::seconds(1));
BOOST_MESSAGE("Waiting for the future");
future.wait();
BOOST_CHECK_EQUAL(future.get(), 42.0f);
}
Но здесь я получаю исключение о нарушении обещания. Причина, по-видимому, довольно ясна для меня, потому что packaged_task, который выполняет преобразование, выходит из области видимости.
Итак, мой вопрос: как мне справиться с такими ситуациями. Как я могу предотвратить уничтожение задания? Есть ли образец для этого?
Bests,
Ronny
Вам необходимо правильно управлять временем жизни объекта задачи.
Самый правильный способ — это вернуться boost::packaged_task<float>
вместо boost::unique_future<float>
от createWrappedFuture()
, Вызывающий будет ответственен за получение будущего объекта и продление срока жизни задачи до тех пор, пока не будет готово будущее значение.
Или вы можете поместить объект задачи в некоторую «ожидающую» очередь (глобальную или классную) так же, как вы это делали в createAsynchronousValue
, Но в этом случае вам нужно будет точно управлять временем жизни задачи и удалять ее из очереди после завершения. Поэтому не думайте, что это решение имеет преимущества перед возвратом самого объекта задачи.
Других решений пока нет …