2017-02-22 4 views
3

У меня есть список фьючерсов. Проблема в том, что у меня много файлов, и мне нужно сделать длинную операцию после создания каждого файла. Вот почему я хочу сделать обратный вызов после каждого сохранения файла.Async будущее с обратным вызовом. C++ 11

Е.Г.,

(new thread; saveFile 1.txt -> new thread; do a long operation after the file has been created) 
    (new thread; saveFile 2.pdf -> new thread; do a long operation after the file has been created). 

мне нужно сделать все, что в отдельном потоке. Сохранение файла имеет решающее значение, вторая задача не может быть выполнена до того, как файл будет создан. Как я могу это сделать? У меня есть следующий код:

void save_file() { 
    // preparing data... 
    saving a file 
    } 

    std::vector<std::future<void>> saveFileTasks; 
    for (int n = 0; n < p.size(); ++n) 
    { 
     saveFileTasks.push_back(std::async(std::bind(&saveFile, filename))); 
    } 

    for (auto &e : saveFileTasks) { 
     e.get(); 
    } 

Как я могу сделать обратного вызова в C++ 11 с будущим/обещаешь? Мне не разрешено использовать boost в моем проекте.

Я действительно смущен, есть очень сложные примеры для очень простой задачи. Множество примеров невозможно компилировать, например, prom_wait_callback не существует в C++ 11, но многие функции были перенесены на C++ 11. Я могу сделать это очень просто, если я использую Python или Clojure. Как я могу сделать это с помощью C++?

+0

Не будет 'std :: future :: wait' выполнять эту работу? Какой смысл запускать 'save_file' и' long_operation' в отдельных потоках, если вам нужно ждать, пока файл будет сохранен? – doc

+0

Мне нужен пул потоков. У меня может быть много задач. Например, 1 необходимо сохранить 10 файлов. Когда сохранено 5 файлов, я могу выполнять длительные операции с 5 потоками, а другие 5 потоков будут сохранять другие файлы. – user565447

ответ

4

К сожалению, нет .then продолжения в текущей версии std::future - предлагается наряду с аналогичными утилитами для будущего стандарта C++.

Если вы не можете использовать boost, вы можете создать свой собственный продолжение с функции состава:

string save_file(string data)  { /* ... */ return filename; } // step 1 
void do_operation(string filename) { /* ... */ }     // step 2 

// ... 

std::vector<std::future<void>> fileTasks; 
for(const auto& data : /* ... */) 
{ 
    fileTasks.emplace_back(std::async(std::launch::async, 
     [data]{ do_operation(save_file(data)); }); 
} 

Обратите внимание, что std::async([data]{ do_operation(save_file(data)); } будет выполнять обе функции в одном потоке. Если вы хотите, каждая функция должна быть выполнена в отдельном потоке можно вызвать async несколько раз:

std::async(std::launch::async, [data] 
{ 
    auto result = save_file(data); 
    std::async(std::launch::async, [r = std::move(result)] 
    { 
     do_operation(std::move(r)); 
    }); 
}); 

С любым boost::future или будущей версии стандарта, вы могли бы просто сказать:

std::async(std::launch::async, [data]{ save_file(data); }) 
    .then([](string filename){ do_operation(filename);); 
+0

У меня ошибка: «class std :: future не имеет атрибута emplace_back». Не могли бы вы объяснить, что это за метод? – user565447

+1

@ user565447 'fileTasks' - это' std :: vector', а не 'std :: future'. Вы, должно быть, неправильно набрали что-то. – Angew

+0

Спасибо, я проверяю, работает ли он. – user565447

4

В будущем будущее будет иметь оператор .then, который позволит вам цепочки задач.

Отсутствие этого мы можем написать.

// complete named operator library in about a dozen lines of code: 
namespace named_operator { 
    template<class D>struct make_operator{ constexpr make_operator() {}; }; 

    template<class T, class O> struct half_apply { T&& lhs; }; 

    template<class Lhs, class Op> 
    half_apply<Lhs, Op> operator*(Lhs&& lhs, make_operator<Op>) { 
    return {std::forward<Lhs>(lhs)}; 
    } 
    template<class Lhs, class Op, class Rhs> 
    decltype(auto) operator*(half_apply<Lhs, Op>&& lhs, Rhs&& rhs) 
    { 
    return named_invoke(std::forward<Lhs>(lhs.lhs), Op{}, std::forward<Rhs>(rhs)); 
    } 
} 

// create a named operator then: 
namespace then_ns { 
    static const struct then_t:named_operator::make_operator<then_t> {} then{}; 

    namespace details { 
    template<size_t...Is, class Tup, class F> 
    auto invoke_helper(std::index_sequence<Is...>, Tup&& tup, F&& f) 
    ->decltype(std::forward<F>(f)(std::get<Is>(std::forward<Tup>(tup))...)) 
    { 
     return std::forward<F>(f)(std::get<Is>(std::forward<Tup>(tup))...); 
    } 
    } 

    // first overload of A *then* B handles tuple and tuple-like return values: 
    template<class Tup, class F> 
    auto named_invoke(Tup&& tup, then_t, F&& f) 
    -> decltype(details::invoke_helper(std::make_index_sequence< std::tuple_size<std::decay_t<Tup>>{} >{}, std::forward<Tup>(tup), std::forward<F>(f))) 
    { 
    return details::invoke_helper(std::make_index_sequence< std::tuple_size<std::decay_t<Tup>>{} >{}, std::forward<Tup>(tup), std::forward<F>(f)); 
    } 

    // second overload of A *then* B 
    // only applies if above does not: 
    template<class T, class F> 
    auto named_invoke(T&& t, then_t, F&& f, ...) 
    -> std::result_of_t< F(T) > 
    { 
    return std::forward<F>(f)(std::forward<T>(t)); 
    } 
    // *then* with a future; unpack the future 
    // into a call to f within an async: 
    template<class X, class F> 
    auto named_invoke(std::future<X> x, then_t, F&& f) 
    -> std::future< std::decay_t<decltype(std::move(x).get() *then* std::declval<F>())> > 
    { 
    return std::async(std::launch::async, 
     [x = std::move(x), f = std::forward<F>(f)]() mutable { 
     return std::move(x).get() *then* std::move(f); 
     } 
    ); 
    } 
    // void future, don't try to pass void to f: 
    template<class F> 
    auto named_invoke(std::future<void> x, then_t, F&& f) 
    -> std::future< std::decay_t<decltype(std::declval<F>()())> > 
    { 
    return std::async(std::launch::async, 
     [x = std::move(x), f = std::forward<F>(f)]() mutable { 
     std::move(x).get(); 
     return std::move(f)(); 
     } 
    ); 
    } 
} 
using then_ns::then; 

см., Это было не так сложно.

a *then* f, если a является кортеж (или пары или массив), будет вызывать f с содержанием a.

Если a не кортеж типа, или f не принимает содержимое a таким образом, это вызывает f с a.

Если a является будущего, вместо этого он создает новое асинхронное будущее, которое потребляет a.get() с помощью *then*.

Live example.

Предположим, что вы хотите, чтобы увеличить атомную Int, когда файл будет сохранен:

std::vector<std::future<void>> saveFileTasks; 
for (int n = 0; n < p.size(); ++n) 
{ 
    saveFileTasks.push_back(
    std::async(std::launch::async, [filename]{ 
     saveFile(filename); 
    }) 
); 
} 
std::atomic<int> count; 
for (auto &e : saveFileTasks) { 
    e = std::move(e) *then* [&count]{ 
    ++count; 
    }); 
} 

Естественно все это может быть сделано без синтаксиса стиля имени оператора *then*, но что самое интересное в этом?

Если первый async возвращает кортеж, второй может взять его как кортеж или как распакованные «плоские» аргументы.

+3

'* then *' ужасен и красив. Я продолжаю мечтать о UFCS ... –

+1

Не очень весело для тех, кому придется копать в ваш код. – doc

+0

@ VittorioRomeo это будет намного чище, чтобы продвигать встроенные типы в классы и предоставлять объекты класса (что-то вроде Ruby или немного похоже на Javascript). Таким образом, вы можете добавлять методы к объекту класса, что также позволит вам получить доступ к защищенным членам класса из метода. – doc

Смежные вопросы