Реализация future:: then() эквивалент для асинхронного выполнения в С++ 11
У меня есть несколько вопросов о реализации функции then()
в Обсуждение Herb Sutter. Эта функция используется для цепочки асинхронных операций, параметр f
- это будущее от одной операции, а параметр w
- это "работа" для этой операции (лямбда).
template <typename Fut, typename Work>
auto then(Fut f, Work w) -> future<decltype(w(f.get()))>
{
return async([=]{ w(f.get()); });
}
Примером приложения может быть:
std::future<int> f = std::async([]{
std::this_thread::sleep_for(std::chrono::microseconds(200));
return 10;
});
auto f2 = then(std::move(f), [](int i){
return 2 * i;
});
Основной поток запускает задачи, но не дожидается завершения любого из них.
Во-первых, future<T>
не имеет конструктора копирования. Это означает, что предлагаемая реализация может использоваться только с shared_future<T>
, если мы не изменим вызов на async()
, чтобы переместить будущее в лямбда. Этот вопрос SO предложил способ сделать это, но это кажется слишком сложным. Я повторно выполнил эту функцию, и мне интересно, правильно ли мой код или я что-то пропустил...
Во-вторых, будущее, которое передается функции then()
, может быть void
, поэтому нам действительно нужны 2 реализации then()
, правильно? Один для фьючерсов, возвращающих T
, и один для фьючерсов, возвращающих void
.
Наконец, если лямбда внутри тела then()
не имеет оператора возврата, чтобы мы могли вернуть значение обратно? Без оператора return возвращается future<void>
, правильно?
Я попытался решить вышеуказанные моменты, и это то, что я придумал. Правильно ли это?
template <typename T, typename Work>
auto then(future<T> f, Work w) -> future<decltype(w(f.get()))>
{
return async([](future<T> f, Work w)
{ return w(f.get()); }, move(f), move(w));
}
template <typename Work>
auto then(future<void> f, Work w) -> future<decltype(w())>
{
return async([](future<void> f, Work w)
{ f.wait(); return w(); }, move(f), move(w));
}
Ответы
Ответ 1
Чтобы упростить интерфейс, я бы "спрятал" проблему void
внутри реализации, аналогично тому, что сделал Herb со своей реализацией concurrent<T>
. Вместо реализации 2 then
объявите вспомогательную функцию get_work_done
с двумя реализациями:
template <typename T, typename Work>
auto get_work_done(future<T> &f, Work &w)-> decltype(w(f.get()))
{return w(f.get());}
template <typename Work>
auto get_work_done(future<void> &f, Work &w)-> decltype(w())
{f.wait(); return w();}
И затем пусть обнаружение параметров шаблона позаботится об остальном:
template <typename T, typename Work>
auto then(future<T> f, Work w) -> future<decltype(w(f.get()))>
{
return async([](future<T> f, Work w)
{ return get_work_done(f,w); }, move(f), move(w));
}
Ответ 2
нет, это неверно. если вы передадите возвращаемое значение .get() в продолжение, оно не сможет обрабатывать исключение, распространяемое из .get(). вам нужно передать будущее в продолжение и вызвать .get() вручную, как в boost.thread
Ответ 3
Проблема с этим подходом к .then() заключается в том, что вы запускаете 2 потока (что дорого) одновременно, а вторая из них блокирует его future.get/wait (если первый будет работать достаточно долго, курс)
Таким образом, лучше использовать рабочую очередь, чтобы сериализовать порядок выполнения заданий (и перепрограммировать существующие потоки).
Просто найдите хорошую реализацию шаблона пула потоков.