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 di runtime, errori di runtime come omissione di una risorsa ed errori che si verificano nelle funzioni lavoro fornite le attività e 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 l'attività o il 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à, i 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 l'attività o il gruppo di attività.

  • Quando possibile, racchiudere ogni chiamata a concurrency::task::get e a concurrency::task::wait con un bloccocatch / tryper gestire gli errori dal quale recuperare.Il runtime terminano l'applicazione se un'attività genera un'eccezione e tale eccezione non viene intercettata dall'attività, una delle continuazioni, o l'applicazione principale.

  • Per esecuzioni basate su attività di una continuazione sempre; non è importante se l'attività precedente viene completata correttamente, ha generato un'eccezione, o è stata annullata.Una continuazione basata su valore non viene eseguito se l'attività precedente viene generato o annulla.

  • Poiché a continuazioni basate su attività sempre esecuzione, determinare se aggiungere a una continuazione relativa alle attività alla fine della catena di continuazione.Ciò può aiutare a garantire che il codice esaminate 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 le 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 a task un oggetto, il runtime archivia l'eccezione e ne esegue il marshalling nel contesto che chiama concurrency::task::get o concurrency::task::wait.Il documento viene descritto Parallelismo delle attività (runtime di concorrenza) basato su attività in base alle continuazioni basate su valore, ma riepilogare, una continuazione basata su valore accetta un parametro di tipo T e a una continuazione relativa alle attività accetta un parametro di tipo task<T>.Se un'attività che genera uno o più delle continuazioni basate su valore, tali continuazioni non vengono pianificate eseguire.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 relativa alle attività consente di gestire qualsiasi eccezione generata dall'attività precedente.Per esecuzioni basate su attività di una continuazione sempre; non è importante se l'attività è stata completata correttamente, ha generato un'eccezione, o è stata annullata.Quando un'attività genera un'eccezione, le relative alle continuazioni basate su attività di pianificazione l'esecuzione.L'esempio seguente illustra un'attività che genera sempre.l'attività ha due continuazioni; uno è basato su un valore e l'altro è basato su attività.Eccezione delle esecuzioni basate su attività sempre pertanto può intercettare l'eccezione generata dall'attività precedente.Quando attende di esempio entrambe le continuazioni completata, l'eccezione viene generato nuovamente poiché l'eccezione di attività è sempre generata quando task::get o task::wait viene chiamato.

// 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 in base alle attività per rilevare eccezioni gestibili.Poiché a continuazioni basate su attività sempre esecuzione, determinare se aggiungere a una continuazione relativa alle attività alla fine della catena di continuazione.Ciò può aiutare a garantire che il codice esaminate tutte le eccezioni.Nell'esempio seguente viene illustrata una catena basata su valore di base di continuazione.La terza attività genera a catena e pertanto alcune delle continuazioni basate su valore che seguono la non viene eseguita.Tuttavia, la continuazione finale è basata su attività e pertanto sempre eseguito.La continuazione finale gestisce l'eccezione generata dalla terza attività.

È consigliabile intercettare le eccezioni più specifiche che è possibile.È possibile omettere alla continuazione relativa alle attività finali se non si dispone di eccezioni specifiche da rilevare.Qualsiasi eccezione rimarrà non gestita e può 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>
*/
SuggerimentoSuggerimento

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

concurrency::task_canceled è un tipo di eccezione runtime importante correlata 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 verrà generato.) È possibile intercettare e gestire questa eccezione anziché su una continuazione relativa alle attività o quando si chiama task::get.Per ulteriori informazioni sull'annullamento di attività, vedere Annullamento nella libreria PPL.

Nota di avvisoAttenzione

Non generano mai task_canceled dal codice.Chiamata concurrency::cancel_current_task anziché.

Il runtime terminano l'applicazione se un'attività genera un'eccezione e tale eccezione non viene intercettata dall'attività, una delle continuazioni, o l'applicazione principale.Se l'applicazione si arresta in modo anomalo, è possibile configurare Visual Studio per interrompere l'esecuzione quando le eccezioni C++ vengono generate.Dopo la diagnosi la posizione dell'eccezione non gestita, utilizzare una continuazione relativa alle attività per gestirla.

La sezione Eccezioni generate dal runtime in questo documento viene descritto come gestire le eccezioni del runtime più dettagliatamente.

[]Parte superiore

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à.

Nota di avvisoAttenzione

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 Understand how Cancellation and Exception Handling Affect Object Destruction 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::structured_task_group o concurrency::task_group, il runtime archivia che 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.

[]Parte superiore

Eccezioni generate dal runtime

Un'eccezione può derivare da una chiamata al runtime.La maggior parte dei tipi di eccezione, ad eccezione 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.Ad esempio, la funzione concurrency::receive generato 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 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.

[]Parte superiore

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.

[]Parte superiore

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.

[]Parte superiore

Attività leggere

Per attività leggera si intende 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).

[]Parte superiore

Agenti asincroni

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

Nell'esempio seguente viene illustrato come 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.

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 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.

[]Parte superiore

[]Parte superiore

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