2012-04-17 10 views
11

Estoy usando vc2011 y resulta que std :: async (std :: launch :: async, ...) es un poco problemático (a veces no genera nuevos hilos y los ejecuta en paralelo, sino que los reutiliza hilos y ejecuta la tarea uno tras otro). Esto es demasiado lento cuando estoy haciendo llamadas de red costosas. Así que pensé que escribiría mi propia función asíncrona. Me estoy quedando atrapado, ¿dónde debería estar std :: en vivo? En la 1) función de hilo, 2) función de sincronización, o 3) función de llamada.Reemplazando std :: async con una versión propia, pero ¿dónde debería std :: promise en vivo?

Código:

#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; 
    } 

} 

Me parece que no puede conseguir más allá del hecho de que el std :: promesa tiene que sobrevivir a la función asíncrona (y vivir tanto como el hilo), por lo que la promesa no puede vivir como una variable local en la función asíncrona. Pero la promesa estándar tampoco debería vivir en el código de la persona que llama, ya que la persona que llama solo necesita saber acerca de los futuros. Y no sé cómo hacer la promesa en vivo en la función de subprocesos, ya que async necesita devolver un futuro incluso antes de que llame al hilo func. Me estoy rascando la cabeza en este caso.

¿Alguien tiene alguna idea?

Editar: Estoy resaltando esto aquí ya que el comentario principal está un poco mal informado. Si bien el valor predeterminado para std :: asycn es el modo directo, cuando una política de inicio de std :: launch :: async se establece explícitamente, debe comportarse como si "los hilos" se generaran y ejecutaran a la vez (ver texto en .cppreference.com/w/cpp/thread/async). Vea el ejemplo en pastebin.com/5dWCjjNY para un caso donde este no es el comportamiento visto en vs20011. La solución funciona muy bien y aceleró mi aplicación en el mundo real por un factor de 10.

Editar 2: MS solucionó el error. Más información aquí: https://connect.microsoft.com/VisualStudio/feedback/details/735731/std-async-std-launch-async-does-not-behave-as-std-thread

+9

"a veces no engendra nuevos hilos y los ejecuta en paralelo, sino que reutiliza los hilos y ejecuta la tarea uno tras otro" Eso no tiene errores; así es como se le permite trabajar. No hay garantía en la especificación de que una llamada asíncrona particular se ejecute en un hilo diferente de las llamadas asincrónicas anteriores o futuras.Si quieres eso, simplemente crea un grupo de hilos, pégalos en un contenedor y únete a ellos cuando quieras recuperar los datos. –

+1

En el futuro, coloque su código directamente en su pregunta en lugar de vincularlo a un sitio externo. – ildjarn

+0

@Nicol. ¿Estás seguro? Como entiendo al usar std :: launch :: async, debería actuar como si se engendrara un hilo. De acuerdo con http://en.cppreference.com/w/cpp/thread/async "If policy & std :: launch :: async! = 0 (el bit async está configurado), genera un nuevo hilo de ejecución como si std :: thread (f, args ...), excepto que si la función f devuelve un valor o lanza una excepción, se almacena en el estado compartido al que se accede a través de std :: future que async regresa a la persona que llama. " – petke

Respuesta

21

Aquí es una solución:

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; 
} 

Asignar promesa en el montón, y luego se pasan por valor [=] un shared_ptr a ella a través de la lambda.

+2

Wow ! Eso funciona. Te daría un millón de puntos si pudiera. Hiciste mi semana. – petke

5

Necesita mover la promesa al nuevo hilo. La respuesta de Andrew Tomazos lo hace al crear un std::promise con propiedad compartida, de modo que ambos hilos pueden ser los propios, y cuando el actual vuelve del alcance actual, solo el nuevo hilo posee la promesa, es decir, la propiedad ha sido transferida. Pero std::promise es movible por lo que debería ser posible moverlo directamente al nuevo hilo, excepto que la solución "obvia" de capturarlo no funciona porque lambda no puede capturar por movimiento, solo por copia (o por referencia, que no funcionaría, ya que obtendría una referencia pendiente.)

Sin embargo, std::thread admite pasar objetos rvalue a la función de inicio del nuevo subproceso. para que pueda declarar la lambda para tomar un argumento std::promise por valor, es decir, pasar el promise a la lambda en lugar de capturarla, y luego mover la promesa en uno de los argumentos de la std::thread por ejemplo

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; 
} 

Esta mover el promesa en el objeto std::thread, que luego lo mueve (en el contexto del nuevo hilo) al parámetro p de lambda.

Cuestiones relacionadas