Реализация future::then() эквивалентно для асинхронного выполнения в C++11
У меня есть несколько вопросов о реализации функции then()
в разговоре Херба Саттера. Эта функция используется для цепочки асинхронных операций, параметр 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()
перенести будущее в лямбду. Этот так вопрос предложил способ сделать это, но это кажется слишком сложным. Я повторно реализовал функцию, и мне интересно, правильный ли мой код или я что-то пропустил...
Во-вторых, будущее, которое передается 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));
}
3 ответа
Проблема с этим подходом к.then() состоит в том, что вы порождаете 2 потока (что дорого) одновременно, и второй из них блокирует его future.get / wait (если первый будет работать достаточно долго, конечно) лучше использовать рабочую очередь для сериализации порядка выполнения заданий (и повторного цикла существующих потоков). Просто найдите хорошую реализацию шаблона пула потоков
Чтобы упростить интерфейс, я бы "спрятал" void
проблема внутри реализации, аналогично тому, что сделал Херб с его concurrent<T>
реализация. Вместо того, чтобы иметь 2 then
реализации, объявить вспомогательную функцию get_work_done
с 2 реализациями:
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));
}
Нет, это не правильно. если вы передадите возвращаемое значение.get() продолжению, он не сможет обработать исключение, переданное из.get(). вы должны передать будущее в продолжение и вызвать.get() вручную, как в boost.thread