Реализация 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 (если первый будет работать достаточно долго, курс) Таким образом, лучше использовать рабочую очередь, чтобы сериализовать порядок выполнения заданий (и перепрограммировать существующие потоки). Просто найдите хорошую реализацию шаблона пула потоков.