Замените std:: async собственной версией, но где должно быть std:: prom вживую?

Я использую vc2011, и получается, что std:: async (std:: launch:: async,...) немного глючит (иногда он не порождает новые потоки и запускает их параллельно, но вместо этого повторно использует потоки и запускает задачу один за другим). Это слишком медленно, когда я выполняю дорогостоящие сетевые вызовы. Поэтому я решил, что напишу свою собственную асинхронную функцию. Я застрял, хотя, где должно быть std:: обещать вживую? В 1) функции потока, 2) асинхронная функция или 3) функция вызывающего абонента.

код:

#include <future>
#include <thread>
#include <iostream>
#include <string>
#include <vector>

std::string thFun() {
    throw std::exception("bang!");
    return "val";
}

std::future<std::string> myasync(std::promise<std::string>& prms) {
//std::future<std::string> myasync() {
    //std::promise<std::string> prms; //needs to outlive thread. How?

    std::future<std::string> fut = prms.get_future();
    std::thread th([&](){
        //std::promise<std::string> prms; //need to return a future before...
        try {
            std::string val = thFun();

            prms.set_value(val);

        } catch(...) {
            prms.set_exception(std::current_exception());
        }

     });

    th.detach();
    return fut;
}

 int main() {

    std::promise<std::string> prms; //I really want the promise hidden iway in the myasync func and not live here in caller code but the promise needs to outlive myasync and live as long as the thread. How do I do this?
    auto fut = myasync(prms);

    //auto fut = myasync(); //Exception: future already retrieved

    try {
        auto res = fut.get();
        std::cout << "Result: " << res << std::endl;

    } catch(const std::exception& exc) {
        std::cout << "Exception: " << exc.what() << std::endl;
    }

 }

Кажется, что я не могу понять, что std:: prom необходимо пережить асинхронную функцию (и жить до тех пор, пока поток), так что обещание не может жить как локальная переменная в функции async. Но std:: prom не должен жить в коде вызывающего абонента, поскольку вызывающий абонент должен знать только о фьючерсах. И я не знаю, как сделать обещание жить в функции потока, поскольку async нужно вернуть будущее, прежде чем он даже вызовет функцию fun. Я царапаю голову над этим.

У кого-нибудь есть идеи?

Изменить: я подчеркиваю это здесь, поскольку верхний комментарий немного дезинформирован. Хотя по умолчанию для std:: asycn разрешен режим отмены, когда явно задана политика запуска std:: launch:: async, она должна вести себя так, как будто "потоки порождаются и запускаются сразу" (см. Формулировку в en.cppreference.com/ж/CPP/резьба/асинхронный). См. Пример в pastebin.com/5dWCjjNY для одного случая, когда это не поведенческое поведение, наблюдаемое в vs20011. Решение отлично работает и ускорило мое приложение в реальном мире в 10 раз.

Изменить 2: MS исправил ошибку. Подробнее здесь: https://connect.microsoft.com/VisualStudio/feedback/details/735731/std-async-std-launch-async-does-not-behave-as-std-thread

Ответ 1

Вот одно из решений:

future<string> myasync()
{
    auto prms = make_shared<promise<string>> ();

    future<string> fut = prms->get_future();

    thread th([=](){

        try {
            string val = thFun();
            // ...
            prms->set_value(val);

        } catch(...) {
            prms->set_exception(current_exception());
        }

     });

    th.detach();

    return fut;
}

Выделите обещание в куче, а затем передайте значение [=] shared_ptr к нему до лямбда.

Ответ 2

Вам нужно перенести обещание в новый поток. Эндрю Томазо отвечает на это, создавая std::promise с общим достоянием, так что оба потока могут владеть обещанием, а когда текущий возвращается из текущей области, только новый поток владеет обещанием, т.е. Передача права собственности. Но std::promise является подвижным, поэтому его можно перенести непосредственно в новый поток, за исключением того, что "очевидное" решение по его захвату не работает, потому что лямбда не может захватываться движением только по копиям (или по ссылке, который не будет работать, так как вы получите свисающую ссылку.)

Однако std::thread поддерживает передачу объектов rvalue в новую функцию запуска потока. поэтому вы можете объявить лямбда для принятия аргумента std::promise по значению, т.е. передать promise в лямбда, а не захватывать его, а затем переместить обещание в один из аргументов std::thread e.g

std::future<std::string> myasync() {
    std::promise<std::string> prms;

    std::future<std::string> fut = prms.get_future();
    std::thread th([&](std::promise<std::string> p){
        try {
            std::string val = thFun();

            p.set_value(val);

        } catch(...) {
            p.set_exception(std::current_exception());
        }

     }, std::move(prms));

    th.detach();
    return fut;
}

Это перенести обещание в объект std::thread, который затем перемещает его (в контексте нового потока) в параметр лямбда p.