Dela via


Undantagshantering i Concurrency Runtime

Concurrency Runtime använder C++-undantagshantering för att kommunicera många typer av fel. De här felen omfattar ogiltig användning av körtiden, körningsfel som att en resurs inte kan hämtas samt fel i arbetsfunktioner som du anger för uppgifter och uppgiftsgrupper. När en aktivitet eller aktivitetsgrupp utlöser ett undantag innehåller körningen undantaget och konverterar det till kontexten som väntar på att aktiviteten eller aktivitetsgruppen ska slutföras. För komponenter som lätta uppgifter och agenter hanterar körningen inte undantag åt dig. I dessa fall måste du implementera din egen mekanism för undantagshantering. Det här avsnittet beskriver hur körningen hanterar undantag som utlöses av uppgifter, aktivitetsgrupper, lätta uppgifter och asynkrona agenter och hur du svarar på undantag i dina program.

Huvudpunkter

  • När en aktivitet eller aktivitetsgrupp utlöser ett undantag innehåller körningen undantaget och konverterar det till kontexten som väntar på att aktiviteten eller aktivitetsgruppen ska slutföras.

  • Om det är möjligt, omge varje anrop till samtidighet::uppgift::hämta och samtidighet::uppgift::vänta med ett try/catch block för att hantera fel som du kan återhämta dig från. Körtiden avslutar appen om en uppgift utlöser ett undantag och det undantaget inte fångas av uppgiften, en av dess fortsättningar eller huvudappen.

  • En aktivitetsbaserad fortsättning körs alltid. Det spelar ingen roll om den tidigare uppgiften slutfördes, utlöste ett undantag eller avbröts. En värdebaserad fortsättning körs inte om den tidigare uppgiften kastar ett fel eller avbryter.

  • Eftersom aktivitetsbaserade fortsättningar alltid körs bör du överväga om du vill lägga till en aktivitetsbaserad fortsättning i slutet av fortsättningskedjan. Detta kan hjälpa till att garantera att koden observerar alla undantag.

  • Körningen genererar concurrency::task_canceled när du anropar concurrency::task::get och den uppgiften avbryts.

  • Körningstiden hanterar inte undantag för lightweight-uppgifter och agenter.

I det här dokumentet

Uppgifter och fortsättningar

I det här avsnittet beskrivs hur körningen hanterar undantag som genereras av samtidighet::aktivitetsobjekt och deras fortsättningar. Mer information om uppgifts- och fortsättningsmodellen finns i Aktivitetsparallellitet.

När du kastar ett undantag i brödtexten för en arbetsfunktion som du skickar till ett task objekt lagrar körmiljön det undantaget och styr det till kontexten som kallar samtidighet::uppgift::hämta eller samtidighet::uppgift::vänta. Dokumentet Aktivitetsparallellitet beskriver aktivitetsbaserade och värdebaserade fortsättningar, men för att sammanfatta tar en värdebaserad fortsättning en parameter av typen T och en aktivitetsbaserad fortsättning tar en parameter av typen task<T>. Om en aktivitet som genererar har en eller flera värdebaserade fortsättningar schemaläggs inte dessa fortsättningar att köras. Följande exempel illustrerar det här beteendet:

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

Med en aktivitetsbaserad fortsättning kan du hantera alla undantag som genereras av den föregående aktiviteten. En aktivitetsbaserad fortsättning körs alltid. Det spelar ingen roll om uppgiften slutfördes, utlöste ett undantag eller avbröts. När en aktivitet utlöser ett undantag schemaläggs dess aktivitetsbaserade fortsättningar att köras. I följande exempel visas en uppgift som alltid genererar. Uppgiften har två fortsättningar. den ena är värdebaserad och den andra är aktivitetsbaserad. Det aktivitetsbaserade undantaget körs alltid och kan därför fånga undantaget som genereras av den föregående aktiviteten. När exemplet väntar på att båda fortsättningarna ska slutföras utlöses undantaget igen eftersom aktivitetsfelet alltid utlöses när task::get eller task::wait anropas.

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

Vi rekommenderar att du använder aktivitetsbaserade fortsättningar för att fånga upp undantag som du kan hantera. Eftersom aktivitetsbaserade fortsättningar alltid körs bör du överväga om du vill lägga till en aktivitetsbaserad fortsättning i slutet av fortsättningskedjan. Detta kan hjälpa till att garantera att koden observerar alla undantag. I följande exempel visas en grundläggande värdebaserad fortsättningskedja. Den tredje aktiviteten i kedjan genererar och därför körs inte alla värdebaserade fortsättningar som följer den. Den slutliga fortsättningen är dock uppgiftsbaserad och körs därför alltid. Den här sista fortsättningen hanterar undantaget som genereras av den tredje aktiviteten.

Vi rekommenderar att du fångar de mest specifika undantagen som möjligt. Du kan utelämna den här slutgiltiga uppgiftsbaserade fortsättningen om du inte har specifika undantag du behöver hantera. Alla undantag förblir ohanterade och kan avsluta appen.

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

Tips/Råd

Du kan använda metoden concurrency::task_completion_event::set_exception för att koppla samman ett undantag med en uppgiftsslutförandehändelse. Dokumentet Task Parallelism beskriver concurrency::task_completion_event-klassen i detalj.

concurrency::task_canceled är en viktig körningsundundatagstyp som relaterar till task. Körtiden kastar task_canceled när du anropar task::get och uppgiften avbryts. (Omvänt task::wait returnerar task_status::avbryts och genererar inte.) Du kan fånga och hantera det här undantaget från en aktivitetsbaserad fortsättning eller när du anropar task::get. Mer information om att avbryta aktiviteter finns i Annullering i PPL.

Försiktighet

task_canceled Kasta aldrig från din kod. Anropa samtidighet::cancel_current_task i stället.

Körtiden avslutar appen om en uppgift utlöser ett undantag och det undantaget inte fångas av uppgiften, en av dess fortsättningar eller huvudappen. Om programmet kraschar kan du konfigurera Visual Studio så att det bryts när C++-undantag utlöses. När du har diagnostiserat platsen för det ohanterade undantaget använder du en aktivitetsbaserad fortsättning för att hantera det.

I avsnittet Undantag som genereras av runtime i det här dokumentet beskrivs hur du arbetar med körningsundantag i detalj.

[Topp]

Aktivitetsgrupper och parallella algoritmer

I denna sektion beskrivs hur körtiden hanterar undantag som kastas av aktivitetsgrupper. Det här avsnittet gäller även parallella algoritmer såsom concurrency::parallel_for, eftersom dessa algoritmer bygger på uppgiftsgrupper.

Försiktighet

Se till att du förstår vilka effekter undantag har på beroende uppgifter. Rekommenderade metoder för hur du använder undantagshantering med uppgifter eller parallella algoritmer finns i avsnittet Förstå hur annullering och undantagshantering påverkar objektförstörelse i avsnittet Metodtips i biblioteket parallella mönster.

Mer information om aktivitetsgrupper finns i Aktivitetsparallellitet. Mer information om parallella algoritmer finns i Parallella algoritmer.

När du utlöser ett undantag i en arbetsfunktions brödtext som du skickar till ett concurrency::task_group- eller concurrency::structured_task_group-objekt, lagrar körningen undantaget och skickar det till den kontext som anropar concurrency::task_group::wait, concurrency::structured_task_group::wait, concurrency::task_group::run_and_wait eller concurrency::structured_task_group::run_and_wait. Körningen stoppar också alla aktiva uppgifter som finns i uppgiftsgruppen (inklusive de i underordnade uppgiftsgrupper) och kastar bort alla uppgifter som ännu inte har påbörjats.

I följande exempel visas den grundläggande strukturen för en arbetsfunktion som utlöser ett undantag. I exemplet används ett task_group objekt för att skriva ut värdena för två point objekt parallellt. Arbetsfunktionen print_point skriver ut värdena för ett point objekt till konsolen. Arbetsfunktionen utlöser ett undantag om indatavärdet är NULL. Körtiden lagrar det här undantaget och för vidare det till den kontext som anropar 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;
   }
}

Det här exemplet genererar följande utdata.

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

Ett fullständigt exempel som använder undantagshantering i en aktivitetsgrupp finns i How to: Use Exception Handling to Break from a Parallel Loop (Använda undantagshantering för att bryta från en parallell loop).

[Topp]

Undantag som genereras av körningen

Ett undantag kan bero på ett anrop till programkörningen. De flesta undantagstyper, förutom concurrency::task_canceled och concurrency::operation_timed_out, indikerar ett programmeringsfel. Dessa fel kan vanligtvis inte återställas och bör därför inte fångas upp eller hanteras av programkod. Vi rekommenderar att du bara fångar upp eller hanterar oåterkalleliga fel i programkoden när du behöver diagnostisera programmeringsfel. Emellertid kan förstå typerna av undantag som definieras av körmiljön hjälpa dig att diagnostisera programmeringsfel.

Mekanismen för undantagshantering är densamma för undantag som genereras av körningen som undantag som genereras av arbetsfunktioner. Till exempel utlöser funktionen operation_timed_out när den inte tar emot ett meddelande under den angivna tidsperioden. Om receive genererar ett undantag i en arbetsfunktion som du skickar till en aktivitetsgrupp lagrar körningen undantaget och konverterar det till kontexten som anropar task_group::wait, structured_task_group::wait, task_group::run_and_waiteller structured_task_group::run_and_wait.

I följande exempel används algoritmen concurrency::p arallel_invoke för att köra två uppgifter parallellt. Den första aktiviteten väntar i fem sekunder och skickar sedan ett meddelande till en meddelandebuffert. Den andra aktiviteten använder receive funktionen för att vänta tre sekunder för att ta emot ett meddelande från samma meddelandebuffert. Funktionen receive genererar operation_timed_out om den inte tar emot meddelandet under tidsperioden.

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

Det här exemplet genererar följande utdata.

The operation timed out.

Om du vill förhindra onormal avslutning av din applikation, kontrollerar du att koden hanterar undantag när den anropar körmiljön. Hantera även undantag när du anropar extern kod som använder Concurrency Runtime, till exempel ett bibliotek från tredje part.

[Topp]

Flera undantag

Om en uppgift eller en parallell algoritm tar emot flera undantag, samlar runtime endast ett av dessa undantag till den anropande kontexten. Programkörningen garanterar inte vilket undantag den hanterar.

I följande exempel används algoritmen parallel_for för att skriva ut siffror till konsolen. Det utlöser ett undantag om indatavärdet är mindre än något minimivärde eller större än något maxvärde. I det här exemplet kan flera arbetsfunktioner utlösa ett undantag.

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

Följande visar exempelutdata för det här exemplet.

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

[Topp]

Avbeställning

Alla undantag tyder inte på ett fel. En sökalgoritm kan till exempel använda undantagshantering för att stoppa den associerade aktiviteten när den hittar resultatet. Mer information om hur du använder annulleringsmekanismer i koden finns i Annullering i PPL.

[Topp]

Lätta uppgifter

En lättviktig uppgift är en uppgift som du schemalägger direkt från ett konkurrens::Scheduler-objekt. Lätta uppgifter medför mindre omkostnader än vanliga uppgifter. Körningen fångar dock inte undantag som genereras av lätta uppgifter. I stället fångas undantaget av den ohanterade undantagshanteraren, som som standard avslutar processen. Använd därför en lämplig mekanism för felhantering i ditt program. Mer information om enklare uppgifter finns i Schemaläggaren.

[Topp]

Asynkrona agenter

Precis som med förenklade uppgifter hanterar körningen inte undantag som genereras av asynkrona agenter.

I följande exempel visas ett sätt att hantera undantag i en klass som härleds från samtidighet::agent. I det points_agent här exemplet definieras klassen. Metoden points_agent::run läser point objekt från meddelandebufferten och skriver ut dem till konsolen. Metoden run genererar ett undantag om den tar emot en NULL pekare.

Metoden run omger allt arbete i ett try-catch block. Blocket catch lagrar undantaget i en meddelandebuffert. Programmet kontrollerar om agenten påträffade ett fel genom att läsa från den här bufferten när agenten har avslutat sitt uppdrag.

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

Det här exemplet genererar följande utdata.

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

Eftersom blocket try-catch finns utanför loopen while slutar agenten att bearbetas när det första felet uppstår. Om blocket try-catch fanns i loopen while, skulle agenten fortsätta när ett fel inträffar.

I det här exemplet lagras undantag i en meddelandebuffert så att en annan komponent kan övervaka agenten efter fel när den körs. I det här exemplet används ett samtidighetsobjekt::single_assignment för att lagra felet. Om en agent hanterar flera undantag single_assignment lagrar klassen endast det första meddelandet som skickas till den. Om du bara vill lagra det sista undantaget använder du klassen concurrency::overwrite_buffer . Om du vill lagra alla undantag använder du klassen concurrency::unbounded_buffer . Mer information om dessa meddelandeblock finns i Asynkrona meddelandeblock.

Mer information om asynkrona agenter finns i Asynkrona agenter.

[Topp]

Sammanfattning

[Topp]

Se även

Samtidighetskörning
Uppgiftsparallellitet
Parallella algoritmer
Annullering i PPL
Aktivitetsschemaläggare
Asynkrona agenter