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 ai gruppi di attività. In questo argomento viene descritto come il runtime gestisce le eccezioni generate dai gruppi di attività, dalle attività leggere e dagli agenti asincroni e come rispondere alle eccezioni nelle applicazioni.
Sezioni
Gruppi di attività e algoritmi paralleli
Attività leggere
Agenti asincroni
Eccezioni comuni
Riepilogo
Gruppi di attività e algoritmi paralleli
In questa sezione viene descritto come il runtime gestisce le eccezioni generate dai gruppi di attività. Questa sezione riguarda anche gli algoritmi paralleli come Concurrency::parallel_for, poiché tali 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 utilizzare la gestione delle eccezioni con le attività o 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.
Eccezioni generate dalle funzioni lavoro
Una funzione lavoro è una funzione lambda, un oggetto funzione o un puntatore a funzione passato al runtime. Quando si passa una funzione lavoro a un gruppo di attività, il runtime la esegue in un contesto separato.
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.
X = 15, Y = 30
Caught exception: point is NULL.
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.
Eccezioni generate dal runtime
Oltre a essere generata dalle funzioni lavoro, un'eccezione può verificarsi a causa di una chiamata al runtime. La maggior parte delle eccezioni generate dal runtime indicano un errore di programmazione. Questi errori sono in genere irreversibili e pertanto non devono essere rilevati o gestiti dal codice dell'applicazione. Tuttavia, tramite la comprensione dei tipi di eccezione definiti dal runtime è possibile diagnosticare gli errori di programmazione. Nella sezione Eccezioni comuni vengono descritte le eccezioni comuni e le condizioni in cui si verificano.
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.
The operation timed out.
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.
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:
8
2
9
3
10
4
5
6
7
Caught exception: -5: the value is less than the minimum.
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.
[vai all'inizio]
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).
[vai all'inizio]
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 occured 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 occured 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.
X: 10 Y: 20
X: 20 Y: 30
error occured in agent: point must not be NULL
the status of the agent is: done
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.
[vai all'inizio]
Eccezioni comuni
Nella tabella seguente sono elencate le classi delle eccezioni comuni nel runtime di concorrenza e vengono fornite le condizioni in cui vengono generate le eccezioni. La maggior parte dei tipi di eccezione, tranne operation_timed_out e unsupported_os, 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.
Classe dell'eccezione |
Condizione |
---|---|
Un puntatore non valido viene passato a un blocco dei messaggi. |
|
Un contesto ha tentato di sbloccarsi. |
|
Il runtime ha tentato di sbloccare un contesto che però era già sbloccato. |
|
È stato effettuato un tentativo di impostare un'utilità di pianificazione come utilità di pianificazione predefinita, mentre già esisteva un'utilità di pianificazione predefinita. |
|
Un blocco è stato acquisito in modo errato. |
|
Un contesto è stato collegato più volte alla stessa utilità di pianificazione. |
|
Un contesto gestito internamente dal runtime è stato disconnesso dall'utilità di pianificazione o il contesto non è collegato ad alcuna utilità di pianificazione. |
|
Un contesto ha incrementato il contatore dei riferimenti in un'utilità di pianificazione in fase di chiusura e tale contesto non è interno all'utilità di pianificazione. |
|
Lo stesso oggetto viene collegato più volte a un blocco dei messaggi. |
|
Un'attività non completata è stata pianificata più volte. |
|
Il runtime ha eseguito un'operazione non valida. |
|
L'oversubscription è stato disabilitato quando non era abilitato. |
|
È stata fornita una chiave dei criteri non valida a un oggetto Concurrency::SchedulerPolicy. |
|
Per un oggetto SchedulerPolicy è stato specificato un livello di concorrenza massimo inferiore al livello di concorrenza minimo. |
|
È stato fornito un valore dei criteri non valido a un oggetto SchedulerPolicy. |
|
Un messaggio richiesto non è stato trovato da un blocco dei messaggi. |
|
Un oggetto gruppo di attività è stato eliminato prima della chiamata al metodo Concurrency::task_group::wait o al metodo Concurrency::structured_task_group::wait. |
|
Un'utilità di pianificazione annidata non si è disconnessa correttamente dall'utilità padre. |
|
Un'operazione non è stata completata nel periodo di tempo specificato. |
|
Un contesto ha tentato di disconnettersi dall'utilità di pianificazione, ma non era collegato ad alcuna utilità di pianificazione. |
|
Il runtime non ha acquisito una risorsa fondamentale, ad esempio una risorsa fornita dal sistema operativo. |
|
Il runtime non è supportato nel sistema operativo corrente. |
[vai all'inizio]
Riepilogo
Quando un'attività genera un'eccezione, il runtime gestisce l'eccezione e ne esegue il marshalling nel contesto che attende il completamento 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.
[vai all'inizio]
Vedere anche
Concetti
Parallelismo delle attività (runtime di concorrenza)
Annullamento nella libreria PPL
Utilità di pianificazione (runtime di concorrenza)
Cronologia delle modifiche
Data |
Cronologia |
Motivo |
---|---|---|
Marzo 2011 |
Aggiunta di una nota di attenzione sugli effetti che le eccezioni possono avere sulle attività dipendenti. |
Miglioramento delle informazioni. |