Gestione delle eccezioni nel runtime di concorrenza

Il runtime di concorrenza usa la gestione delle eccezioni C++ per comunicare molti tipi di errori. Questi errori includono l'uso non valido del runtime, gli errori di runtime, ad esempio l'impossibilità di acquisire una risorsa e gli errori che si verificano nelle funzioni di lavoro fornite alle attività e ai gruppi di attività. Quando un'attività o un gruppo di attività genera un'eccezione, il runtime mantiene tale eccezione e lo effettua il marshalling nel contesto che attende il completamento dell'attività o del gruppo di attività. Per componenti come attività e agenti leggeri, il runtime non gestisce le eccezioni. In questi casi, è necessario implementare il proprio meccanismo di gestione delle eccezioni. Questo argomento descrive in che modo il runtime gestisce le eccezioni generate da attività, gruppi di attività, attività leggere e agenti asincroni e come rispondere alle eccezioni nelle applicazioni.

Punti chiave

  • Quando un'attività o un gruppo di attività genera un'eccezione, il runtime mantiene tale eccezione e lo effettua 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 concurrency::task::wait con un try/catch blocco per gestire gli errori che è possibile recuperare. Il runtime termina l'app se un'attività genera un'eccezione e tale eccezione non viene intercettata dall'attività, una delle relative continuazioni o dall'app principale.

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

  • Poiché le continuazioni basate su attività vengono sempre eseguite, valutare se aggiungere una continuazione basata su attività alla fine della catena di continuazione. Ciò consente di garantire che il codice osservi 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 attività e agenti leggeri.

In questo documento

Attività e continuazioni

In questa sezione viene descritto come il runtime gestisce le eccezioni generate dagli oggetti concurrency::task e dalle relative continuazioni. Per altre informazioni sul modello di attività e continuazione, vedere Parallelismo delle attività.

Quando si genera un'eccezione nel corpo di una funzione di lavoro passata a un task oggetto , il runtime archivia l'eccezione e la effettua il marshalling nel contesto che chiama concurrency::task::get o concurrency::task::wait. Il documento Task Parallelism descrive le continuazioni basate su attività e basate su valori, ma per riepilogare una continuazione basata su valori accetta un parametro di tipo T e una continuazione basata su attività accetta un parametro di tipo task<T>. Se un'attività che genera ha una o più continuazioni basate su valori, tali continuazioni non vengono pianificate per l'esecuzione. Il seguente esempio illustra questo comportamento.

// 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 importa se l'attività è stata completata correttamente, ha generato un'eccezione o è stata annullata. Quando un'attività genera un'eccezione, le continuazioni basate su attività vengono pianificate per l'esecuzione. L'esempio seguente mostra un'attività che genera sempre . L'attività ha due continuazioni; uno è basato sul valore e l'altro è basato su attività. L'eccezione basata su attività viene sempre eseguita e pertanto può intercettare l'eccezione generata dall'attività precedente. Quando l'esempio attende il completamento di entrambe le continuazioni, l'eccezione viene generata di nuovo perché l'eccezione dell'attività viene sempre generata quando task::get viene chiamato o task::wait .

// 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 usare le continuazioni basate su attività per intercettare le eccezioni che è possibile gestire. Poiché le continuazioni basate su attività vengono sempre eseguite, valutare se aggiungere una continuazione basata su attività alla fine della catena di continuazione. Ciò consente di garantire che il codice osservi tutte le eccezioni. Nell'esempio seguente viene illustrata una catena di continuazione basata su valori di base. La terza attività della catena genera un'eccezione e pertanto tutte le continuazioni basate su valore che seguono non vengono eseguite. Tuttavia, la continuazione finale è basata su attività e quindi viene sempre eseguita. Questa continuazione finale gestisce l'eccezione generata dalla terza attività.

È consigliabile rilevare le eccezioni più specifiche che è possibile eseguire. È possibile omettere questa continuazione finale basata su attività se non sono presenti eccezioni specifiche da intercettare. Qualsiasi eccezione rimarrà non gestita e può terminare l'app.

// 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 task-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 usare il metodo concurrency::task_completion_event::set_exception per associare un'eccezione a un evento di completamento dell'attività. Il documento Task Parallelism descrive in modo più dettagliato la classe concurrency::task_completion_event .

concurrency::task_canceled è un tipo di eccezione di runtime importante correlato a task. Il runtime genera task_canceled un'eccezione quando si chiama task::get e l'attività viene annullata. Al contrario, task::wait restituisce task_status::canceled e non genera un'eccezione. È possibile intercettare e gestire questa eccezione da una continuazione basata su attività o quando si chiama task::get. Per altre informazioni sull'annullamento delle attività, vedere Annullamento nel PPL.

Attenzione

Non generare mai task_canceled dal codice. Chiamare invece concurrency::cancel_current_task .

Il runtime termina l'app se un'attività genera un'eccezione e tale eccezione non viene intercettata dall'attività, una delle relative continuazioni o dall'app principale. Se l'applicazione si arresta in modo anomalo, è possibile configurare Visual Studio per interrompere quando vengono generate eccezioni C++. Dopo aver diagnosticato la posizione dell'eccezione non gestita, usare una continuazione basata su attività per gestirla.

La sezione Eccezioni generate dal runtime in questo documento descrive in modo più dettagliato come usare le eccezioni di runtime.

[Torna all'inizio]

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, ad esempio concurrency::p arallel_for, perché questi algoritmi si basano sui gruppi di attività.

Attenzione

Assicurarsi di comprendere gli effetti che le eccezioni hanno sulle attività dipendenti. Per le procedure consigliate su come usare la gestione delle eccezioni con attività o algoritmi paralleli, vedere la sezione Informazioni su come l'annullamento e la gestione delle eccezioni influiscono sulla distruzione degli oggetti nell'argomento Procedure consigliate nella libreria di modelli paralleli.

Per altre informazioni sui gruppi di attività, vedere Parallelismo delle attività. Per altre informazioni sugli algoritmi paralleli, vedere Algoritmi paralleli.

Quando si genera un'eccezione nel corpo di una funzione di lavoro passata a un oggetto concurrency::task_group o concurrency::structured_task_group , il runtime archivia l'eccezione e la effettua il marshalling al 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 anche tutte le attività attive presenti nel gruppo di attività (incluse quelle nei gruppi di attività figlio) e rimuove tutte le attività che non sono ancora state avviate.

Nell'esempio seguente viene illustrata la struttura di base di una funzione di lavoro che genera un'eccezione. Nell'esempio viene utilizzato un task_group oggetto per stampare i valori di due point oggetti in parallelo. La print_point funzione di lavoro stampa i valori di un point oggetto nella console. La funzione di lavoro genera un'eccezione se il valore di input è NULL. Il runtime archivia questa eccezione e la esegue il marshalling al 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 il seguente output:

X = 15, Y = 30Caught exception: point is NULL.

Per un esempio completo che usa la gestione delle eccezioni in un gruppo di attività, vedere Procedura: Usare la gestione delle eccezioni per interrompere un ciclo parallelo.

[Torna all'inizio]

Eccezioni generate dal runtime

Un'eccezione può derivare da una chiamata al runtime. La maggior parte dei tipi di eccezione, ad eccezione di concurrency::task_canceled e concurrency::operation_timed_out, indica 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 solo errori irreversibili nel codice dell'applicazione quando è necessario diagnosticare gli errori di programmazione. Tuttavia, la comprensione dei tipi di eccezione definiti dal runtime consente di diagnosticare gli errori di programmazione.

Il meccanismo di gestione delle eccezioni è lo stesso per le eccezioni generate dal runtime come eccezioni generate dalle funzioni di lavoro. Ad esempio, la funzione concurrency::receive genera operation_timed_out quando non riceve un messaggio nel periodo di tempo specificato. Se receive genera un'eccezione in una funzione di lavoro passata a un gruppo di attività, il runtime archivia tale eccezione e lo effettua il marshalling al contesto che chiama task_group::wait, task_group::run_and_waitstructured_task_group::wait, o structured_task_group::run_and_wait.

Nell'esempio seguente viene usato l'algoritmo concurrency::p arallel_invoke per eseguire due attività in parallelo. La prima attività attende cinque secondi e quindi invia un messaggio a un buffer di messaggi. La seconda attività usa la receive funzione per attendere tre secondi per ricevere un messaggio dallo stesso buffer di messaggi. La receive funzione genera operation_timed_out se non riceve il messaggio nel periodo di tempo.

// 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 il seguente output:

The operation timed out.

Per evitare la terminazione anomala dell'applicazione, assicurarsi che il codice gestisca le eccezioni quando chiama il runtime. Gestire anche le eccezioni quando si chiama in codice esterno che usa il runtime di concorrenza, ad esempio una libreria di terze parti.

[Torna all'inizio]

Più eccezioni

Se un'attività o un algoritmo parallelo riceve più eccezioni, il runtime esegue il marshalling di una sola di queste eccezioni nel contesto chiamante. Il runtime non garantisce l'eccezione di cui esegue il marshalling.

Nell'esempio seguente viene utilizzato l'algoritmo parallel_for per stampare i numeri nella console. Genera un'eccezione se il valore di input è minore di un valore minimo o maggiore di un valore massimo. In questo esempio più funzioni di 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;
   }  
}

Di seguito viene illustrato l'output di esempio per questo esempio.

8293104567Caught exception: -5: the value is less than the minimum.

[Torna all'inizio]

Annullamento

Non tutte le eccezioni indicano un errore. Ad esempio, un algoritmo di ricerca potrebbe usare la gestione delle eccezioni per arrestare l'attività associata quando trova il risultato. Per altre informazioni su come usare i meccanismi di annullamento nel codice, vedere Annullamento nel PPL.

[Torna all'inizio]

Attività leggere

Un'attività leggera è un'attività pianificata direttamente da un oggetto concurrency::Scheduler . Le attività leggere comportano un sovraccarico inferiore rispetto alle normali attività. Tuttavia, il runtime non intercetta le eccezioni generate da attività leggere. L'eccezione viene invece intercettata dal gestore eccezioni non gestito, che per impostazione predefinita termina il processo. Pertanto, usare un meccanismo di gestione degli errori appropriato nell'applicazione. Per altre informazioni sulle attività leggere, vedere Utilità di pianificazione.

[Torna all'inizio]

Agenti asincroni

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

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

Il run metodo racchiude tutto il lavoro in un try-catch blocco. Il blocco archivia catch l'eccezione in un buffer di messaggi. L'applicazione verifica se l'agente ha rilevato un errore leggendo da questo buffer al termine 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 il seguente output:

X: 10 Y: 20
X: 20 Y: 30
error occurred in agent: point must not be NULL
the status of the agent is: done

Poiché il try-catch blocco esiste all'esterno del while ciclo, l'agente termina l'elaborazione quando rileva il primo errore. Se il try-catch blocco si trovava all'interno del while ciclo, l'agente continuerà dopo un errore.

Questo esempio archivia le eccezioni in un buffer di messaggi in modo che un altro componente possa monitorare l'agente per individuare gli errori durante l'esecuzione. Questo esempio usa un oggetto concurrency::single_assignment per archiviare l'errore. Nel caso in cui un agente gestisce più eccezioni, la single_assignment classe archivia solo il primo messaggio passato. Per archiviare solo l'ultima eccezione, usare la classe concurrency::overwrite_buffer . Per archiviare tutte le eccezioni, usare la classe concurrency::unbounded_buffer . Per altre informazioni su questi blocchi di messaggi, vedere Blocchi di messaggi asincroni.

Per altre informazioni sugli agenti asincroni, vedere Agenti asincroni.

[Torna all'inizio]

Riepilogo

[Torna all'inizio]

Vedi anche

Runtime di concorrenza
Parallelismo delle attività
Algoritmi paralleli
Annullamento nella libreria PPL
Utilità di pianificazione
Agenti asincroni