Udostępnij za pośrednictwem


Obsługa wyjątków w Runtime współbieżności

Runtime współbieżności używa obsługi do komunikacji wiele rodzajów błędów wyjątków C++.Błędy te obejmują nieprawidłowe użycie runtime, błędy wykonania jak awaria pozyskać zasobu i błędów występujących w funkcje pracy, które dostarczają do zadań i grup zadań.Zadania lub grupy zadań zgłasza wyjątek, posiada ten wyjątek wykonawczym i marshals go do kontekstu, która oczekuje zadanie lub grupy zadań, aby zakończyć.Składniki, takie jak lekkie zadań i agentów runtime nie zarządza wyjątki dla Ciebie.W tych przypadkach należy zaimplementować własny mechanizm obsługi wyjątków.W tym temacie opisano sposób wykonywania obsługi wyjątków, które są generowane przez zadania, grup zadań, lekkie zadań i asynchronicznych agentów i sposobu odpowiedzi na wyjątki w aplikacji.

Punkty klucza

  • Zadania lub grupy zadań zgłasza wyjątek, posiada ten wyjątek wykonawczym i marshals go do kontekstu, która oczekuje zadanie lub grupy zadań, aby zakończyć.

  • Jeśli to możliwe, należy ująć każde wywołanie concurrency::task::get i concurrency::task::wait z try/catch bloku obsługi błędów, które można odzyskać.Środowisko wykonawcze kończy aplikacji Jeśli zadania zgłasza wyjątek i nie wykryto wyjątek przez zadanie, jeden z jego kontynuacji lub głównym aplikacji.

  • Uruchamia zawsze opartych na zadaniach utrzymania; nie ma znaczenia, czy zwrócił wyjątek antecedent zadania ukończone pomyślnie, lub zostało anulowane.Kontynuacja wartość oparta nie uruchamiane, jeśli generuje lub anuluje zadania antecedent.

  • Ponieważ zawsze uruchamiane kontynuacji opartych na zadaniach, należy rozważyć, czy dodać na końcu łańcucha na kontynuację kontynuacji opartych na zadaniach.Pomoże to zagwarantować, że kod przestrzega wszystkie wyjątki.

  • Generuje runtime concurrency::task_canceled po wywołaniu concurrency::task::get i zadanie zostało anulowane.

  • Środowiska wykonawczego nie zarządza wyjątki dla zadań lekki i agentów.

W tym dokumencie

  • Zadania i kontynuacji

  • Algorytmy równoległe i grup zadań

  • Wyjątki generowane przez środowisko wykonawcze

  • Wiele wyjątków

  • Anulowanie

  • Lekkie zadań

  • Agenci asynchroniczne

Zadania i kontynuacji

W tej sekcji opisano sposób wykonywania obsługi wyjątków, które są generowane przez concurrency::task obiektów i ich kontynuacji.Więcej informacji o modelu zadań i kontynuacji, zobacz Zadanie równoległości (współbieżności Runtime).

Kiedy Zgłoś wyjątek, w treści funkcji pracy przekazać do task obiektu wykonawczym przechowuje ten wyjątek i marshals go do kontekstu, który wywołuje concurrency::task::get lub concurrency::task::wait.Dokument Zadanie równoległości (współbieżności Runtime) opisuje zadaniami kontra kontynuacji opartych na wartości, ale aby podsumować kontynuacji opartych na wartość ma parametr typu T i kontynuacji opartych na zadaniach przyjmuje parametr typu task<T>.Jeśli zadanie, która zgłasza kontynuacji opartych na wartość jeden lub więcej, kontynuacji tych nie są zaplanowane do uruchomienia.Poniższy przykład ilustruje to zachowanie:

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

Kontynuacja opartych na zadaniach pozwala obsłużyć wyjątek, który jest generowany przez zadanie antecedent.Uruchamia zawsze opartych na zadaniach utrzymania; nie ma znaczenia, czy zadania ukończone pomyślnie, zwrócił wyjątek lub zostało anulowane.Gdy zadanie zgłasza wyjątek, jego kontynuacji opartych na zadaniach zaplanowane.Zadanie, które zawsze można znaleźć w poniższym przykładzie.Zadanie ma dwa kontynuacji; jeden jest oparta na wartości, a druga jest z zadaniami.Wyjątku zadania zawsze działa, a zatem może przechwycić wyjątek, który jest generowany przez zadanie antecedent.Gdy przykład czeka na obu kontynuacji zakończenie, wyjątku ponownie, ponieważ zadanie zawsze wyjątku podczas task::get lub task::wait jest wywoływana.

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

Zaleca się, aby korzystać z zadaniami kontynuacji do połowu wyjątki, które mogą obsługiwać.Ponieważ zawsze uruchamiane kontynuacji opartych na zadaniach, należy rozważyć, czy dodać na końcu łańcucha na kontynuację kontynuacji opartych na zadaniach.Pomoże to zagwarantować, że kod przestrzega wszystkie wyjątki.Poniższy przykład pokazuje łańcucha podstawowe kontynuacji oparta na wartości.Generuje trzecim zadaniem w łańcuchu i dlatego nie są uruchamiane na wartość kontynuacji, które po nim następują.Jednak kontynuacji końcowy jest oparty na zadania i dlatego zawsze działa.To kontynuacja końcowy obsługuje wyjątek, który jest generowany przez trzeci zadania.

Firma Microsoft zaleca, aby wychwycić najbardziej wyjątków, które można.To końcowe kontynuacji opartych na zadaniach można pominąć, jeśli nie ma określonych wyjątków do połowu.Każdy wyjątek pozostaną nieobsługiwany i może prowadzić do aplikacji.

// 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>
*/
PoradaPorada

Można użyć concurrency::task_completion_event::set_exception do kojarzenia wyjątek z zdarzeń zakończenia zadania.Dokument Zadanie równoległości (współbieżności Runtime) opisuje concurrency::task_completion_event klasy bardziej szczegółowo.

CONCURRENCY::task_canceled jest typ wyjątku ważne runtime, który odnosi się do task.Generuje runtime task_canceled po wywołaniu task::get i zadanie zostało anulowane.(I odwrotnie, task::wait zwraca task_status::canceled i nie.) Można wychwycić i obsłużyć ten wyjątek kontynuacji opartych na zadaniach lub podczas wywołania task::get.Aby uzyskać więcej informacji na temat anulowanie zadań zobacz Anulowanie w PPL.

Informacje dotyczące przestrogiPrzestroga

Throw nigdy nie task_canceled z kodu.Wywołanie concurrency::cancel_current_task zamiast.

Środowisko wykonawcze kończy aplikacji Jeśli zadania zgłasza wyjątek i nie wykryto wyjątek przez zadanie, jeden z jego kontynuacji lub głównym aplikacji.W przypadku awarii aplikacji, można skonfigurować Visual Studio przerwać, gdy są generowane wyjątki C++.Po zdiagnozować jest lokalizacja nieobsłużony wyjątek, użyj kontynuacji opartych na zadaniach je obsłużyć.

Sekcja Wyjątki generowane przez program obsługi w tym dokumencie opisano sposób pracy z wyjątkami runtime bardziej szczegółowo.

Top

Algorytmy równoległe i grup zadań

W tej sekcji opisano sposób wykonywania obsługi wyjątków, które są generowane przez grupy zadań.W tej sekcji dotyczy również algorytmy równoległe takich jak concurrency::parallel_for, ponieważ tych algorytmów tworzenia grup zadań.

Informacje dotyczące przestrogiPrzestroga

Upewnij się, że rozumiesz skutki, w których wyjątki zadań zależnych.Zalecane praktyki na temat obsługi z zadań lub algorytmy równoległe wyjątków, zobacz Understand how Cancellation and Exception Handling Affect Object Destruction sekcji najlepszych praktyk w temacie równoległych biblioteki wzorców.

Aby uzyskać więcej informacji dotyczących grup zadań, zobacz Zadanie równoległości (współbieżności Runtime).Aby uzyskać więcej informacji na temat algorytmów równoległych zobacz Algorytmy równoległe.

Kiedy Zgłoś wyjątek, w treści funkcji pracy przekazać do concurrency::task_group lub concurrency::structured_task_group obiektu wykonawczym przechowuje ten wyjątek i marshals go do kontekstu, który wywołuje concurrency::task_group::wait, concurrency::structured_task_group::wait, concurrency::task_group::run_and_wait, lub concurrency::structured_task_group::run_and_wait.Środowisko wykonawcze również zatrzymuje wszystkie aktywne zadania, które należą do grupy zadań (włączając te w grupach zadań podrzędnych) i odrzuca wszelkie zadania, które jeszcze się nie rozpoczęły.

Poniższy przykład pokazuje podstawową strukturę funkcja pracę, która zgłasza wyjątek.W przykładzie użyto task_group obiektu wartości w dwóch point obiektów równolegle.print_point Funkcja pracy służy do drukowania wartości point obiektu do konsoli.Funkcja pracę zgłasza wyjątek, jeśli wartość wejściowa jest NULL.Środowisko wykonawcze przechowuje ten wyjątek i marshals go do kontekstu, który wywołuje 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;
   }
}

Ten przykład generuje następujące wyniki.

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

Pełny przykład używa obsługi wyjątków w grupy zadań, zobacz Jak: Użyj obsługi wyjątków do podziału z pętli równoległe.

Top

Wyjątki generowane przez środowisko wykonawcze

Wywołanie wykonawczym może spowodować wyjątek.Większość typów wyjątków, z wyjątkiem concurrency::task_canceled i concurrency::operation_timed_out, wskazuje błąd programistyczny.Te błędy są zwykle nieodwracalny i dlatego nie należy złowionych ani obsługiwane przez kod aplikacji.Sugerujemy tylko catch lub uchwyt nieodwracalne błędy w kodzie aplikacji, gdy trzeba diagnozować błędy programowania.Jednakże opis typów wyjątków, które są zdefiniowane w czasie wykonywania może pomóc zdiagnozować błędy programowania.

Mechanizm obsługi wyjątków jest taka sama dla wyjątków, które są generowane przez program obsługi wyjątków, które są generowane przez funkcje pracy.Na przykład concurrency::receive działać rzuca operation_timed_out gdy nie otrzyma wiadomości w określonym okresie.Jeśli receive zgłasza wyjątek w funkcji pracy, przechodzą do grupy zadań, runtime przechowuje ten wyjątek i marshals go do kontekstu, który wywołuje task_group::wait, structured_task_group::wait, task_group::run_and_wait, lub structured_task_group::run_and_wait.

W poniższym przykładzie użyto concurrency::parallel_invoke algorytm równolegle dwa zadania.Pierwsze pięć sekund oczekiwania i następnie wysyła wiadomość do buforu wiadomości.Drugie zadanie używa receive funkcja oczekiwania trzy sekundy komunikat z buforu samej wiadomości.receive Działać rzuca operation_timed_out , jeżeli nie otrzyma wiadomości w okresie czasu.

// 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;
   }
}

Ten przykład generuje następujące wyniki.

The operation timed out.

Zapobiegające Nienormalne zakończenie aplikacji, upewnij się, że kod wywołuje w czasie wykonywania, obsługuje wyjątki.Również obsługi wyjątków podczas wywołania na zewnętrzny kod, który używa Runtime współbieżności na przykład biblioteka firm.

Top

Wiele wyjątków

Jeśli zadanie lub równoległy algorytm otrzymuje wiele wyjątków, runtime marshals tylko jeden z tych wyjątków w kontekście wywołującego.Środowisko wykonawcze nie gwarantuje wyjątek, który go marshals.

W poniższym przykładzie użyto parallel_for algorytm numery do konsoli.Jeśli wartość wejściowa jest mniejsza niż niektóre wartości minimalnej lub większa niż wartość maksymalna, niektóre, zgłasza wyjątek.W tym przykładzie wielu funkcji pracy można zgłosić wyjątek.

// 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;
   }  
}

Poniżej przedstawiono przykładowe dane wyjściowe tego przykładu.

8
2
9
3
10
4
5
6
7
Caught exception: -5: the value is less than the minimum.

Top

Anulowanie

Nie wszystkie wyjątki wskazać błąd.Algorytm wyszukiwania może na przykład użyć obsługi wyjątków zatrzymać jego skojarzonego zadania po znalezieniu wynik.Aby uzyskać więcej informacji o unieważnieniu mechanizmów w kodzie, zobacz Anulowanie w PPL.

Top

Lekkie zadań

Lekkie zadanie jest zadaniem, które zaplanować bezpośrednio z concurrency::Scheduler obiektu.Lekkie zadań przewozu mniejszego nakładu pracy niż zwyczajne zadania.Jednakże runtime nie może przechwytywać wyjątków przez lekkie zadań.Zamiast tego wyjątku jest objęte obsługi nieobsłużony wyjątek, który domyślnie kończy proces.Dlatego należy użyć właściwego mechanizmu obsługi błędów w aplikacji.Więcej informacji na temat zadań lekkie zobacz Harmonogram zadań (współbieżności Runtime).

Top

Agenci asynchroniczne

Jak lekkie zadań runtime nie zarządza wyjątków, które są generowane przez agentów asynchronicznego.

Poniższy przykład przedstawia sposób obsługi wyjątków w klasie, która wynika z concurrency::agent.W tym przykładzie definiuje points_agent klasy.points_agent::run Odczyty metoda point obiekty z buforu wiadomości i drukuje je do konsoli.run Metody zgłasza wyjątek, jeżeli otrzyma NULL wskaźnik.

run Metody otaczającej wszystkie prace w try-catch bloku.catch Bloku wyjątku są przechowywane w buforze wiadomości.Aplikacja sprawdza, czy agent napotkał błąd przez odczyt z buforu, po zakończeniu agenta.

// 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;
}

Ten przykład generuje następujące wyniki.

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

Ponieważ try-catch bloku istnieje poza while pętli, agent kończy przetwarzanie po napotkaniu pierwszego błędu.Jeśli try-catch był blok wewnątrz while pętli, agent nadal po wystąpieniu błędu.

W tym przykładzie przechowuje wyjątki w buforze wiadomości tak, aby monitorować inny składnik agent błędy jak działa.W tym przykładzie concurrency::single_assignment obiektu do przechowywania błędu.W przypadku gdy agent obsługuje wiele wyjątków single_assignment klasy przechowuje tylko pierwszy komunikat przekazany do niej.Do przechowywania ostatniego wyjątek, użyj concurrency::overwrite_buffer klasy.Aby przechowywać wszystkie wyjątki, użyj concurrency::unbounded_buffer klasy.Więcej informacji o tych bloków wiadomości, zobacz Asynchroniczne blokuje wiadomości.

Aby uzyskać więcej informacji na temat agentów asynchronicznych, zobacz Agenci asynchroniczne.

Top

Podsumowanie

Top

Zobacz też

Koncepcje

Współbieżność Runtime

Zadanie równoległości (współbieżności Runtime)

Algorytmy równoległe

Anulowanie w PPL

Harmonogram zadań (współbieżności Runtime)

Agenci asynchroniczne