2014-07-03 6 views
5

La prego di dirmi se l'approccio che uso per gestire il caso d'uso non è valido e in caso affermativo, qual è il modo giusto per gestire:concorrenza :: cause compito destructor chiamano ad abortire in caso d'uso valida

task<int> do_work(int param) 
{ 
    // runs some work on a separate thread, returns task with result or throws exception on failure 
} 

void foo() 
{ 
    try 
    { 
     auto result_1 = do_work(10000); 
     auto result_2 = do_work(20000); 

     // do some extra work 

     process(result_1.get(), result_2.get()); 
    } 
    catch (...) 
    { 
     // logs the failure details 
    } 
} 

Quindi il codice tenta di eseguire due processi in parallelo e quindi elaborare i risultati. Se uno dei lavori genera un'eccezione, chiama lo task::get per rigenera l'eccezione. Il problema si verifica se entrambe le attività generano un'eccezione. In questo caso, la prima chiamata a task::get causerà lo svolgimento dello stack, quindi verrà chiamato il distruttore del secondo task e a sua volta farà ricominciare un'altra eccezione durante lo svolgimento dello stack che provoca la chiamata di "abort".

Questo approccio mi è sembrato del tutto valido finché non ho affrontato il problema.

+0

Eventualmente correlato http://stackoverflow.com/questions/4766768/unhandled-forced-unwind-causes-abort?rq=1 – CoryKramer

+1

Perché il (secondo) compito viene lanciato dal suo distruttore? – StackedCrooked

+0

@StackedCrooked ... Questa è in realtà la domanda principale. O ho sbagliato o 'task' destructor rigenera davvero l'eccezione che è stata catturata nella funzione' do_work'. – topoden

risposta

2

In parole semplici si ha un'eccezione non gestita (non osservata) poiché l'eccezione generata in una delle attività non ottiene caught by the task, one of its continuations, or the main app, perché l'eccezione ri-generata da task :: get per la prima attività si svolge lo stack in precedenza la chiamata al task :: get accade per la seconda attività.

Un codice più semplificato mostra che std::terminate viene chiamato perché l'eccezione generata nell'attività non viene gestita. Se non si fa il commit del result.get(), la chiamata a std::terminate, come task::get, rigetterà l'eccezione.

#include <pplx/pplx.h> 
#include <pplx/pplxtasks.h> 
#include <iostream> 

int main(int argc, char* argv[]) 
{ 
    try 
    { 
     auto result = pplx::create_task([]()-> int 
     { 
      throw std::exception("task failed"); 
     }); 

     // actually need wait here till the exception is thrown, e.g. 
     // result.wait(), but this will re-throw the exception making this a valid use-case 

     std::cout << &result << std::endl; // use it 
     //std::cout << result.get() << std::endl; 
    } 
    catch (std::exception const& ex) 
    { 
     std::cout << ex.what() << std::endl; 
    } 

    return 0; 
} 

un'occhiata al suggerimento pplx::details::_ExceptionHandler::~_ExceptionHolder()

//pplxwin.h 
#define _REPORT_PPLTASK_UNOBSERVED_EXCEPTION() do { \ 
    __debugbreak(); \ 
    std::terminate(); \ 
} while(false) 


//pplxtasks.h 
pplx::details::_ExceptionHandler::~_ExceptionHolder() 
{ 
    if (_M_exceptionObserved == 0) 
    { 
     // If you are trapped here, it means an exception thrown in task chain didn't get handled. 
     // Please add task-based continuation to handle all exceptions coming from tasks. 
     // this->_M_stackTrace keeps the creation callstack of the task generates this exception. 
     _REPORT_PPLTASK_UNOBSERVED_EXCEPTION(); 
    } 
} 

Nel codice originale la prima chiamata task::get solleva l'eccezione generata in questo compito, che impedisce ovviamente la seconda chiamata task::get così l'eccezione del secondo compito non viene gestito (rimane "inosservato").

verrà chiamato il distruttore del secondo task e, a sua volta, verrà ripetuta un'altra eccezione durante lo svolgimento dello stack che provoca la chiamata di "abort".

Il distruttore del secondo compito non ri-generare l'eccezione si chiama semplicemente std :: terminate() (che chiama std :: Abort())

vedere. Exception Handling in the Concurrency Runtime

+0

Vedo, grazie. Quindi il distruttore non rilancia (che è buono), chiama semplicemente 'std :: terminate' (che in realtà è cattivo e non cambia la mia domanda). La domanda è se sia giusto o meno utilizzare l'approccio che ho usato. Mi è sembrato che l'approccio fosse assolutamente valido: l'utente licenzia diverse attività parallele, quindi in seguito ottiene i risultati e li elabora. Ora vedo che semplicemente non funzionerà se almeno due attività falliscono ... – topoden

Problemi correlati