Condividi tramite


Gestione delle eccezioni nel runtime di concorrenza

Il runtime di concorrenza utilizza la gestione delle eccezioni C++ per comunicare molti tipi di errori. Questi errori includono l'utilizzo non valido del runtime, errori di runtime come l'acquisizione non riuscita di una risorsa ed errori che si verificano nelle funzioni lavoro fornite alle attività e ai gruppi di attività. Quando un'attività o un gruppo di attività genera un'eccezione, il runtime gestisce l'eccezione e ne esegue il marshalling nel contesto che attende il completamento dell'attività o del gruppo di attività. Il runtime non gestisce le eccezioni per i componenti come le attività leggere e gli agenti. In questi casi, è necessario implementare un meccanismo di gestione delle eccezioni personalizzato. In questo argomento viene descritto come il runtime gestisce le eccezioni generate dalle attività, dai gruppi di attività, dalle attività leggere e dagli agenti asincroni e come rispondere alle eccezioni nelle applicazioni.

Punti chiave

  • Quando un'attività o un gruppo di attività genera un'eccezione, il runtime gestisce l'eccezione e ne esegue il marshalling nel contesto che attende il completamento dell'attività o del gruppo di attività.

  • Quando possibile, racchiudere ogni chiamata a concurrency::task::get e a concurrency::task::wait con un blocco try/catch per gestire gli errori che si possono recuperare. L'applicazione verrà terminata dal runtime se un'attività genera un'eccezione e tale eccezione non viene intercettata dall'attività, da una delle sue continuazioni o dall'applicazione principale.

  • Una continuazione basata su attività viene sempre eseguita; non è importante se l'attività precedente è stata completata correttamente, ha generato un'eccezione oppure è stata annullata. Una continuazione basata su valori non viene eseguita se l'attività precedente ha generato un'eccezione o se è stata cancellata.

  • Poiché le continuazioni basate su attività vengono sempre eseguite, è necessario considerare l'aggiunta di una continuazione basata su attività alla fine della catena di continuazione. Questa operazione può permettere al codice di rilevare tutte le eccezioni.

  • Il runtime genera concurrency::task_canceled quando si chiama concurrency::task::get e l'attività viene annullata.

  • Il runtime non gestisce le eccezioni per le attività leggere e gli agenti.

In questo documento

  • Attività e continuazioni

  • Gruppi di attività e algoritmi paralleli

  • Eccezioni generate dal runtime

  • Più eccezioni

  • Annullamento

  • Attività leggere

  • Agenti asincroni

Attività e continuazioni

In questa sezione viene descritto come il runtime gestisce le eccezioni generate dagli oggetti concurrency::task e dalle loro continuazioni. Per ulteriori informazioni sull'attività e sul modello di continuazione, vedere Parallelismo delle attività (runtime di concorrenza).

Quando si genera un'eccezione nel corpo di una funzione lavoro passata ad un oggetto task, il runtime archivia l'eccezione e ne esegue il marshalling nel contesto che chiama concurrency::task::get o concurrency::task::wait. Il documento Parallelismo delle attività (runtime di concorrenza) descrive le continuazioni basate su attività e quelle basate su valori, ma per riepilogare, una continuazione basata su valori accetta un parametro di tipo T mentre una continuazione basata su attività accetta un parametro di tipo task<T>. Se un'attività che genera un'eccezione ha una o più continuazioni basate su valori, tali continuazioni non verranno pianificate per l'esecuzione. Questo comportamento è illustrato nell'esempio seguente:

// eh-task.cpp 
// compile with: /EHsc
#include <ppltasks.h>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain()
{
    wcout << L"Running a task..." << endl;
    // Create a task that throws.
    auto t = create_task([]
    {
        throw exception();
    });

    // Create a continuation that prints its input value.
    auto continuation = t.then([]
    {
        // We do not expect this task to run because 
        // the antecedent task threw.
        wcout << L"In continuation task..." << endl;
    });

    // Wait for the continuation to finish and handle any  
    // error that occurs. 
    try
    {
        wcout << L"Waiting for tasks to finish..." << endl;
        continuation.wait();

        // Alternatively, call get() to produce the same result. 
        //continuation.get();
    }
    catch (const exception& e)
    {
        wcout << L"Caught exception." << endl;
    }
}
/* Output:
    Running a task...
    Waiting for tasks to finish...
    Caught exception.
*/

Una continuazione basata su attività consente di gestire qualsiasi eccezione generata dall'attività precedente. Una continuazione basata su attività viene sempre eseguita; non è importante se l'attività è stata completata correttamente, ha generato un'eccezione oppure è stata annullata. Quando un'attività genera un'eccezione, le sue continuazioni basate su attività verranno pianificate per l'esecuzione. Nell'esempio seguente viene mostrata un'attività che genera sempre un'eccezione. L'attività presenta due continuazioni; una è basata su valori mentre l'altra è basata su attività. La continuazione basata su attività viene sempre eseguita e quindi può rilevare l'eccezione generata dall'attività precedente. Quando nell'esempio si rimane in attesa della terminazione di entrambe le continuazioni, l'eccezione viene generata di nuovo poiché l'eccezione dell'attività viene sempre generata quando task::get o task::wait vengono chiamati.

// eh-continuations.cpp 
// compile with: /EHsc
#include <ppltasks.h>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain()
{    
    wcout << L"Running a task..." << endl;
    // Create a task that throws.
    auto t = create_task([]() -> int
    {
        throw exception();
        return 42;
    });

    // 
    // Attach two continuations to the task. The first continuation is   
    // value-based; the second is task-based. 

    // Value-based continuation.
    auto c1 = t.then([](int n)
    {
        // We don't expect to get here because the antecedent  
        // task always throws.
        wcout << L"Received " << n << L'.' << endl;
    });

    // Task-based continuation.
    auto c2 = t.then([](task<int> previousTask)
    {
        // We do expect to get here because task-based continuations 
        // are scheduled even when the antecedent task throws. 
        try
        {
            wcout << L"Received " << previousTask.get() << L'.' << endl;
        }
        catch (const exception& e)
        {
            wcout << L"Caught exception from previous task." << endl;
        }
    });

    // Wait for the continuations to finish. 
    try
    {
        wcout << L"Waiting for tasks to finish..." << endl;
        (c1 && c2).wait();
    }
    catch (const exception& e)
    {
        wcout << L"Caught exception while waiting for all tasks to finish." << endl;
    }
}
/* Output:
    Running a task...
    Waiting for tasks to finish...
    Caught exception from previous task.
    Caught exception while waiting for all tasks to finish.
*/

È consigliabile utilizzare le continuazioni basate su attività per rilevare le eccezioni gestibili. Poiché le continuazioni basate su attività vengono sempre eseguite, è necessario considerare l'aggiunta di una continuazione basata su attività alla fine della catena di continuazione. Questa operazione può permettere al codice di rilevare tutte le eccezioni. Nell'esempio seguente viene illustrata una catena di continuazioni basate su valori. La terza attività nella catena genera un'eccezione, pertanto tutte le continuazioni basate su valori che seguono non verranno eseguite. Tuttavia, la continuazione finale è basata su attività e pertanto verrà sempre eseguita. La continuazione finale gestisce l'eccezione generata dalla terza attività.

È consigliabile intercettare le eccezioni nel modo più specifico possibile. È possibile omettere la continuazione finale basata su attività se non si hanno eccezioni specifiche da rilevare. Qualsiasi eccezione rimarrà non gestita e potrà terminare l'applicazione.

// eh-task-chain.cpp 
// compile with: /EHsc
#include <ppltasks.h>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain()
{
    int n = 1;
    create_task([n]
    {
        wcout << L"In first task. n = ";
        wcout << n << endl;

        return n * 2;

    }).then([](int n)
    {
        wcout << L"In second task. n = ";
        wcout << n << endl;

        return n * 2;

    }).then([](int n)
    {
        wcout << L"In third task. n = ";
        wcout << n << endl;

        // This task throws. 
        throw exception();
        // Not reached. 
        return n * 2;

    }).then([](int n)
    {
        // This continuation is not run because the previous task throws.
        wcout << L"In fourth task. n = ";
        wcout << n << endl;

        return n * 2;

    }).then([](task<int> previousTask)
    {
        // This continuation is run because it is value-based. 
        try
        {
            // The call to task::get rethrows the exception.
            wcout << L"In final task. result = ";
            wcout << previousTask.get() << endl;
        }
        catch (const exception&)
        {
            wcout << L"<exception>" << endl;
        }
    }).wait();
}
/* Output:
    In first task. n = 1
    In second task. n = 2
    In third task. n = 4
    In final task. result = <exception>
*/

Suggerimento

È possibile utilizzare il metodo concurrency::task_completion_event::set_exception per associare un'eccezione ad un evento di completamento delle attività.Il documento Parallelismo delle attività (runtime di concorrenza) descrive la classe concurrency::task_completion_event più dettagliatamente.

concurrency::task_canceled è un tipo di eccezione runtime importante correlato a task. Il runtime genera task_canceled quando si chiama task::get e l'attività viene annullata. (Viceversa, task::wait restituisce task_status::canceled e non genera eccezioni.) È possibile intercettare e gestire questa eccezione con una continuazione basata su attività o quando si chiama task::get. Per ulteriori informazioni sull'annullamento delle attività, vedere Annullamento nella libreria PPL.

Avviso

Non generare mai task_canceled dal codice.Si chiami invece concurrency::cancel_current_task.

L'applicazione verrà terminata dal runtime se un'attività genera un'eccezione e tale eccezione non viene intercettata dall'attività, da una delle sue continuazioni o dall'applicazione principale. Se l'applicazione si arresta in modo anomalo, è possibile configurare Visual Studio per interrompere l'esecuzione quando vengono generate eccezioni C++. Dopo aver diagnosticato la posizione dell'eccezione non gestita, utilizzare una continuazione basata su attività per la sua gestione.

La sezione Eccezioni generate dal runtime in questo documento descrive in dettaglio come gestire le eccezioni del runtime.

[Top]

Gruppi di attività e algoritmi paralleli

In questa sezione viene descritto come il runtime gestisce le eccezioni generate dai gruppi di attività. Questa sezione si applica anche agli algoritmi paralleli come concurrency::parallel_for, poiché tali algoritmi sono basati sui gruppi di attività.

Avviso

Assicurarsi di comprendere gli effetti che le eccezioni hanno sulle attività dipendenti.Per le procedure consigliate su come utilizzare la gestione delle eccezioni con le attività o con gli algoritmi paralleli, vedere la sezione Come l'annullamento e la gestione delle eccezioni influiscono sull'eliminazione degli oggetti nelle procedure consigliate dell'argomento PPL (Parallel Patterns Library).

Per ulteriori informazioni sui gruppi di attività, vedere Parallelismo delle attività (runtime di concorrenza). Per ulteriori informazioni sugli algoritmi paralleli, vedere Algoritmi paralleli.

Quando si genera un'eccezione nel corpo di una funzione lavoro passata a un oggetto concurrency::task_group o concurrency::structured_task_group, il runtime archivia l'eccezione e ne esegue il marshalling nel contesto che chiama concurrency::task_group::wait, concurrency::structured_task_group::wait, concurrency::task_group::run_and_wait o concurrency::structured_task_group::run_and_wait. Il runtime arresta inoltre tutte le attività attive presenti nel gruppo di attività, comprese quelle presenti nei gruppi di attività figlio, ed elimina le attività che non sono ancora state avviate.

Nell'esempio seguente viene illustrata la struttura di base di una funzione lavoro che genera un'eccezione. Nell'esempio viene utilizzato un oggetto task_group per visualizzare i valori di due oggetti point in parallelo. La funzione lavoro print_point visualizza i valori di un oggetto point nella console. La funzione lavoro genera un'eccezione se il valore di input è NULL. Il runtime archivia l'eccezione e ne esegue il marshalling nel contesto che chiama task_group::wait.

// eh-task-group.cpp 
// compile with: /EHsc
#include <ppl.h>
#include <iostream>
#include <sstream>

using namespace concurrency;
using namespace std;

// Defines a basic point with X and Y coordinates. 
struct point
{
   int X;
   int Y;
};

// Prints the provided point object to the console. 
void print_point(point* pt)
{
   // Throw an exception if the value is NULL. 
   if (pt == NULL)
   {
      throw exception("point is NULL.");
   }

   // Otherwise, print the values of the point.
   wstringstream ss;
   ss << L"X = " << pt->X << L", Y = " << pt->Y << endl;
   wcout << ss.str();
}

int wmain()
{
   // Create a few point objects.
   point pt = {15, 30};
   point* pt1 = &pt;
   point* pt2 = NULL;

   // Use a task group to print the values of the points.
   task_group tasks;

   tasks.run([&] {
      print_point(pt1);
   });

   tasks.run([&] {
      print_point(pt2);
   });

   // Wait for the tasks to finish. If any task throws an exception, 
   // the runtime marshals it to the call to wait. 
   try
   {
      tasks.wait();
   }
   catch (const exception& e)
   {
      wcerr << L"Caught exception: " << e.what() << endl;
   }
}

Questo esempio produce l'output che segue.

  

Per un esempio completo in cui viene utilizzata la gestione delle eccezioni in un gruppo di attività, vedere Procedura: utilizzare la gestione delle eccezion per interrompere un ciclo Parallel.

[Top]

Eccezioni generate dal runtime

Un'eccezione può verificarsi a causa di una chiamata al runtime. La maggior parte dei tipi di eccezione, esclusi concurrency::task_canceled e concurrency::operation_timed_out, indicano un errore di programmazione. Questi errori sono in genere irreversibili e pertanto non devono essere rilevati o gestiti dal codice dell'applicazione. È consigliabile rilevare o gestire gli errori irreversibili nel codice dell'applicazione solo quando è necessario diagnosticare gli errori di programmazione. Tuttavia, tramite la comprensione dei tipi di eccezione definiti dal runtime è possibile diagnosticare gli errori di programmazione.

Il meccanismo di gestione delle eccezioni è lo stesso sia per le eccezioni generate dal runtime che per le eccezioni generate dalle funzioni lavoro. La funzione concurrency::receive, ad esempio, genera operation_timed_out quando non viene ricevuto un messaggio nel periodo di tempo specificato. Se receive genera un'eccezione in una funzione lavoro passata a un gruppo di attività, il runtime archivia l'eccezione e ne esegue il marshalling nel contesto che chiama task_group::wait, structured_task_group::wait, task_group::run_and_wait o structured_task_group::run_and_wait.

Nell'esempio seguente viene utilizzato l'algoritmo concurrency::parallel_invoke per eseguire due attività in parallelo. La prima attività attende cinque secondi, quindi invia un messaggio a un buffer dei messaggi. La seconda attività utilizza la funzione receive per attendere tre secondi per la ricezione di un messaggio dallo stesso buffer dei messaggi. Se il messaggio non viene ricevuto nel periodo di tempo indicato, la funzione receive genera operation_timed_out.

// eh-time-out.cpp 
// compile with: /EHsc
#include <agents.h>
#include <ppl.h>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain()
{
   single_assignment<int> buffer;
   int result;

   try
   {
      // Run two tasks in parallel.
      parallel_invoke(
         // This task waits 5 seconds and then sends a message to  
         // the message buffer.
         [&] {
            wait(5000); 
            send(buffer, 42);
         },
         // This task waits 3 seconds to receive a message. 
         // The receive function throws operation_timed_out if it does  
         // not receive a message in the specified time period.
         [&] {
            result = receive(buffer, 3000);
         }
      );

      // Print the result.
      wcout << L"The result is " << result << endl;
   }
   catch (operation_timed_out&)
   {
      wcout << L"The operation timed out." << endl;
   }
}

Questo esempio produce l'output che segue.

  

Per evitare l'interruzione anomala dell'applicazione, verificare che il codice gestisca le eccezioni quando viene effettuata una chiamata nel runtime. Le eccezioni devono inoltre essere gestite quando si effettua una chiamata nel codice esterno che utilizza il runtime di concorrenza, ad esempio, una libreria di terze parti.

[Top]

Più eccezioni

Se un'attività o un algoritmo parallelo riceve più eccezioni, il runtime esegue il marshalling solo di una delle eccezioni nel contesto di chiamata. Il runtime non garantisce di quale eccezione viene eseguito il marshalling.

Nell'esempio seguente viene utilizzato l'algoritmo parallel_for per visualizzare i numeri nella console. Se il valore di input è inferiore a un valore minimo o superiore a un valore massimo, viene generata un'eccezione. In questo esempio, più funzioni lavoro possono generare un'eccezione.

// eh-multiple.cpp 
// compile with: /EHsc
#include <ppl.h>
#include <iostream>
#include <sstream>

using namespace concurrency;
using namespace std;

int wmain()
{
   const int min = 0;
   const int max = 10;

   // Print values in a parallel_for loop. Use a try-catch block to  
   // handle any exceptions that occur in the loop. 
   try
   {
      parallel_for(-5, 20, [min,max](int i)
      {
         // Throw an exeception if the input value is less than the  
         // minimum or greater than the maximum. 

         // Otherwise, print the value to the console. 

         if (i < min)
         {
            stringstream ss;
            ss << i << ": the value is less than the minimum.";
            throw exception(ss.str().c_str());
         }
         else if (i > max)
         {
            stringstream ss;
            ss << i << ": the value is greater than than the maximum.";
            throw exception(ss.str().c_str());
         }
         else
         {
            wstringstream ss;
            ss << i << endl;
            wcout << ss.str();
         }
      });
   }
   catch (exception& e)
   {
      // Print the error to the console.
      wcerr << L"Caught exception: " << e.what() << endl;
   }  
}

Questo esempio produce l'output seguente:

  

[Top]

Annullamento

Non tutte le eccezioni indicano un errore. Un algoritmo di ricerca potrebbe, ad esempio, utilizzare la gestione delle eccezioni per arrestare l'attività associata quando viene trovato il risultato. Per ulteriori informazioni su come utilizzare i meccanismi di annullamento nel codice, vedere Annullamento nella libreria PPL.

[Top]

Attività leggere

Un'attività leggera è un'attività che si pianifica direttamente da un oggetto concurrency::Scheduler. Le attività leggere implicano meno sovraccarico delle attività ordinarie. Tuttavia, il runtime non rileva le eccezioni generate dalle attività leggere. L'eccezione viene invece rilevata dal gestore delle eccezioni non gestite che per impostazione predefinita termina il processo. Pertanto, utilizzare un meccanismo di gestione degli errori appropriato nell'applicazione. Per ulteriori informazioni sulle attività leggere, vedere Utilità di pianificazione (runtime di concorrenza).

[Top]

Agenti asincroni

Analogamente alle attività leggere, il runtime non gestisce le eccezioni generate dagli agenti asincroni.

Nell'esempio seguente viene illustrato un modo per gestire le eccezioni in una classe che deriva da concurrency::agent. Nell'esempio viene definita la classe points_agent. Il metodo points_agent::run legge gli oggetti point dal buffer dei messaggi e li visualizza nella console. Il metodo run genera un'eccezione se viene ricevuto un puntatore NULL.

Il metodo run racchiude tutto il lavoro in un blocco try-catch. Il blocco catch archivia l'eccezione in un buffer dei messaggi. L'applicazione controlla se l'agente ha rilevato un errore leggendo da questo buffer dopo il completamento dell'agente.

// eh-agents.cpp 
// compile with: /EHsc
#include <agents.h>
#include <iostream>

using namespace concurrency;
using namespace std;

// Defines a point with x and y coordinates. 
struct point
{
   int X;
   int Y;
};

// Informs the agent to end processing.
point sentinel = {0,0};

// An agent that prints point objects to the console. 
class point_agent : public agent
{
public:
   explicit point_agent(unbounded_buffer<point*>& points)
      : _points(points)
   { 
   }

   // Retrieves any exception that occurred in the agent. 
   bool get_error(exception& e)
   {
      return try_receive(_error, e);
   }

protected:
   // Performs the work of the agent. 
   void run()
   {
      // Perform processing in a try block. 
      try
      {
         // Read from the buffer until we reach the sentinel value. 
         while (true)
         {
            // Read a value from the message buffer.
            point* r = receive(_points);

            // In this example, it is an error to receive a  
            // NULL point pointer. In this case, throw an exception. 
            if (r == NULL)
            {
               throw exception("point must not be NULL");
            }
            // Break from the loop if we receive the  
            // sentinel value. 
            else if (r == &sentinel)
            {
               break;
            }
            // Otherwise, do something with the point. 
            else
            {
               // Print the point to the console.
               wcout << L"X: " << r->X << L" Y: " << r->Y << endl;
            }
         }
      }
      // Store the error in the message buffer. 
      catch (exception& e)
      {
         send(_error, e);
      }

      // Set the agent status to done.
      done();
   }

private:
   // A message buffer that receives point objects.
   unbounded_buffer<point*>& _points;

   // A message buffer that stores error information.
   single_assignment<exception> _error;
};

int wmain()
{  
   // Create a message buffer so that we can communicate with 
   // the agent.
   unbounded_buffer<point*> buffer;

   // Create and start a point_agent object.
   point_agent a(buffer);
   a.start();

   // Send several points to the agent.
   point r1 = {10, 20};
   point r2 = {20, 30};
   point r3 = {30, 40};

   send(buffer, &r1);
   send(buffer, &r2);
   // To illustrate exception handling, send the NULL pointer to the agent.
   send(buffer, reinterpret_cast<point*>(NULL));
   send(buffer, &r3);
   send(buffer, &sentinel);

   // Wait for the agent to finish.
   agent::wait(&a);

   // Check whether the agent encountered an error.
   exception e;
   if (a.get_error(e))
   {
      cout << "error occurred in agent: " << e.what() << endl;
   }

   // Print out agent status.
   wcout << L"the status of the agent is: ";
   switch (a.status())
   {
   case agent_created:
      wcout << L"created";
      break;
   case agent_runnable:
      wcout << L"runnable";
      break;
   case agent_started:
      wcout << L"started";
      break;
   case agent_done:
      wcout << L"done";
      break;
   case agent_canceled:
      wcout << L"canceled";
      break;
   default:
      wcout << L"unknown";
      break;
   }
   wcout << endl;
}

Questo esempio produce l'output che segue.

  

Poiché il blocco try-catch è esterno al ciclo while, l'agente termina l'elaborazione quando viene rilevato il primo errore. Se il blocco try-catch fosse interno al ciclo while, l'agente continuerebbe dopo un errore.

In questo esempio le eccezioni vengono archiviate in un buffer dei messaggi in modo tale che un altro componente possa monitorare l'agente per verificare la presenza di errori durante l'esecuzione. In questo esempio viene utilizzato un oggetto concurrency::single_assignment per archiviare l'errore. Nel caso in cui un agente gestisca più eccezioni, la classe single_assignment archivia solo il primo messaggio passato. Per archiviare solo l'ultima eccezione, utilizzare la classe concurrency::overwrite_buffer. Per archiviare tutte le eccezioni, utilizzare la classe concurrency::unbounded_buffer. Per ulteriori informazioni su questi blocchi dei messaggi, vedere Blocchi dei messaggi asincroni.

Per ulteriori informazioni sugli agenti asincroni, vedere Agenti asincroni.

[Top]

[Top]

Vedere anche

Concetti

Runtime di concorrenza

Parallelismo delle attività (runtime di concorrenza)

Algoritmi paralleli

Annullamento nella libreria PPL

Utilità di pianificazione (runtime di concorrenza)

Agenti asincroni