Sdílet prostřednictvím


Zpracování výjimek v Concurrency Runtime

Modul Runtime souběžnosti používá ke komunikaci různé druhy chyb pro zpracování výjimek C++.Tyto chyby neplatné použití runtime, chyby za běhu, jako je například selhání získání zdroje a chyby zahrnout do pracovní funkce, které poskytujete, úkoly a skupiny úkolů.Pokud úkol nebo skupinu úkolů vyvolá výjimku, modul runtime obsahuje tato výjimka a zařazuje do kontextu, který čeká úlohy či skupiny úloh do konce.Modul runtime komponent jako jsou lehké úkoly a agenti nespravuje výjimky pro vás.V těchto případech je nutné implementovat vlastní mechanismus zpracování výjimek.Toto téma popisuje, jak modul runtime zpracovává výjimky, které jsou vyvolány úkoly, úkol skupiny, lehké úkoly a asynchronní agentů a jak reagovat na výjimky v aplikacích.

Klíčové body

  • Pokud úkol nebo skupinu úkolů vyvolá výjimku, modul runtime obsahuje tato výjimka a zařazuje do kontextu, který čeká úlohy či skupiny úloh do konce.

  • Pokud je to možné, obklopují každého volání concurrency::task::get a concurrency::task::wait se try/catch bloku zpracování chyb, které můžete zotavit.Modul runtime aplikace ukončí, pokud úloha vyvolá výjimku a úlohou, jeden z jeho pokračování nebo hlavní app není zachycena výjimka.

  • Pokračování vždy podle úloh spustí; nezáleží, zda předchozí úloha dokončena úspěšně, došlo k výjimce, nebo byla zrušena.Pokračování založeného na hodnotách se nespustí, pokud předchozí úloha vyvolá nebo zruší.

  • Vždy spustit pokračování podle úloh, proto zvažte, zda chcete-li přidat na konci vašeho řetězce pokračování pokračování podle úloh.To může pomoci zajistit, že váš kód dodržuje všechny výjimky.

  • Modul runtime vyvolá concurrency::task_canceled při volání concurrency::task::get a tento úkol je zrušena.

  • Modul runtime výjimky pro lehké úkoly a agenti nespravuje.

V tomto dokumentu

  • Úlohy a pokračování

  • Skupiny úloh a paralelní algoritmy

  • Výjimky vyvolané modulem Runtime

  • Více výjimek

  • Zrušení

  • Prosté úlohy

  • Asynchronní agenti

Úlohy a pokračování

Tato část popisuje, jak modul runtime zpracovává výjimky, které jsou vyvolány concurrency::task objekty a jejich pokračování.Další informace o úkolu a pokračování modelu naleznete v tématu Funkční paralelismus (Concurrency Runtime).

Když vyvoláním výjimky v těle funkce práce, který předáte task objektu modulu runtime ukládá tato výjimka a zařazuje do kontextu, který volá concurrency::task::get nebo concurrency::task::wait.Dokument Funkční paralelismus (Concurrency Runtime) popisuje podle úloh versus pokračování založeného na hodnotách, ale chcete sumarizovat, založeného na hodnotách pokračování přebírá parametr typu T a pokračování podle úloh přebírá parametr typu task<T>.Pokud úkol, který vyvolá jednu nebo více pokračování založeného na hodnotách, nejsou tyto pokračování naplánováno spouštění.Následující příklad ukazuje tento problém:

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

Pokračování podle úloh umožňuje zpracovat všechny výjimky, která je vyvolána předchozí úlohou.Pokračování vždy podle úloh spustí; nezáleží, zda je úloha dokončena úspěšně, došlo k výjimce, nebo byla zrušena.Pokud úloha vyvolá výjimku, jeho pokračování podle úloh spuštění je naplánováno na.Následující příklad zobrazuje úlohu, která vždy vyvolá.Úkol má dvě pokračování; jedna je na základě hodnoty a druhý podle úloh.Výjimka podle úloh vždy spuštěn a proto může zachytit výjimku, která je vyvolána předchozí úlohou.Po příkladu čeká i pokračování do konce, je vzhledem k tomu, kdy vždy vyvolána výjimka úlohy znovu vyvolána výjimka task::get nebo task::wait se nazývá.

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

Doporučujeme použít podle úloh pokračování k zachycení výjimek, které lze zpracovat.Vždy spustit pokračování podle úloh, proto zvažte, zda chcete-li přidat na konci vašeho řetězce pokračování pokračování podle úloh.To může pomoci zajistit, že váš kód dodržuje všechny výjimky.Následující příklad ukazuje základní pokračování založené na hodnotu řetězce.Vyvolá třetím úkolu v řetězu, a proto nejsou žádné založeného na hodnotách pokračování, které následují jej spustit.Poslední pokračování je však podle úloh a proto vždy spuštěn.Tento poslední pokračování zpracovává výjimku, která je vyvolána ve třetím úkolu.

Doporučujeme, abyste zachytit nejvíce specifické výjimky, které je možné.Tento poslední pokračování podle úloh můžete vynechat, pokud nemáte zachytit specifické výjimky.Jakékoli výjimky zůstanou neošetřené a může ukončit aplikace.

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

Tip

Lze použít concurrency::task_completion_event::set_exception metoda přidružit k události dokončení úkolu výjimku.Dokument Funkční paralelismus (Concurrency Runtime) popisuje concurrency::task_completion_event třídy podrobněji.

Concurrency::task_canceled je typ výjimky důležité runtime, který se vztahuje k task.Modul runtime vyvolá task_canceled při volání task::get a tento úkol je zrušena. (Naopak, task::wait vrátí task_status::canceled a nevyvolá.) Můžete zachytit a zpracovat tuto výjimku z pokračování podle úloh nebo při volání task::get.Další informace o zrušení úlohy naleznete v tématu Zrušení v knihovně PPL.

Poznámka k upozorněníUpozornění

Nikdy nevyvolá task_canceled z vašeho kódu.Volání concurrency::cancel_current_task místo.

Modul runtime aplikace ukončí, pokud úloha vyvolá výjimku a úlohou, jeden z jeho pokračování nebo hlavní app není zachycena výjimka.Pokud dojde k chybě aplikace, můžete nakonfigurovat Visual Studio přerušit, pokud jsou vyvolány výjimky C++.Po můžete diagnostikovat umístění neošetřenou výjimku, zpracování pomocí pokračování podle úloh.

V části Výjimky vyvolána modulem Runtime v tomto dokumentu popisuje, jak pracovat s výjimek modulu runtime podrobněji.

[Nahoře]

Skupiny úloh a paralelní algoritmy

Tato část popisuje, jak modul runtime zpracovává výjimky, které jsou vyvolány skupiny úloh.V této části platí také pro paralelní algoritmy jako concurrency::parallel_for, protože tyto algoritmy sestavování skupiny úkolů.

Poznámka k upozorněníUpozornění

Ujistěte se, že chápete efekty, které výjimky jsou závislé úkoly.Doporučené postupy jak používat s úkoly nebo paralelní algoritmy pro zpracování výjimek naleznete v tématu Principy jak zrušení a výjimka zpracování zničení objektu vliv sekce v osvědčených postupů v tématu paralelní knihovnu vzorků.

Další informace o skupinách úloh, naleznete v Funkční paralelismus (Concurrency Runtime).Další informace o paralelních algoritmech naleznete v tématu Paralelní algoritmy.

Když vyvoláním výjimky v těle funkce práce, který předáte concurrency::task_group nebo concurrency::structured_task_group objektu modulu runtime ukládá tato výjimka a zařazuje do kontextu, který volá concurrency::task_group::wait, concurrency::structured_task_group::wait, concurrency::task_group::run_and_wait, nebo concurrency::structured_task_group::run_and_wait.Modul runtime také zastaví všechny aktivní úkoly, které jsou ve skupině úloh (včetně podřízených skupin úloh) a zahodí dosud nezahájené úkoly.

Následující příklad zobrazuje základní strukturu pracovní funkci, která vyvolá výjimku.V příkladu je použita task_group objektu k tisku hodnot dvou point objekty současně.print_point Pracovní funkce vytiskne hodnoty point objekt do konzoly.Pracovní funkce vyvolá výjimku, pokud je vstupní hodnota NULL.Modul runtime ukládá tato výjimka a zařazuje do kontextu, který volá 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;
   }
}

Tento příklad vytvoří následující výstup.

  

Kompletní příklad, který používá zpracování výjimek v skupiny úloh, naleznete v Postupy: Přerušení paralelní smyčky pomocí zpracování výjimek.

[Nahoře]

Výjimky vyvolané modulem Runtime

Výjimky mohou být výsledkem volání modulu runtime.Většina typů výjimek, s výjimkou concurrency::task_canceled a concurrency::operation_timed_out, indikace programovací chyby.Tyto chyby jsou obvykle neodstranitelné a proto by neměla být zachycena nebo zpracovány kódem aplikace.Doporučujeme, aby pouze skutečné nebo zpracování neodstranitelné chyby v kódu aplikace, pokud je třeba diagnostikovat chyby.Nicméně Princip typy výjimek, které jsou definovány v modulu runtime vám může pomoci diagnostikovat chyby.

Mechanismus zpracování výjimek je stejný pro výjimky, které jsou vyvolány modulem runtime jako výjimky, které jsou vyvolány pracovní funkce.Například concurrency::receive vyvolá funkci operation_timed_out pokud neobdrží zprávu v daném časovém období.Pokud receive vyvolá výjimku v pracovní funkci předat úkol skupiny, modul runtime ukládá tato výjimka a zařazuje do kontextu, který volá task_group::wait, structured_task_group::wait, task_group::run_and_wait, nebo structured_task_group::run_and_wait.

V následujícím příkladu concurrency::parallel_invoke algoritmus pracovat paralelně dva úkoly.První úkol čeká pět sekund a potom odešle zprávu do vyrovnávací paměti zprávy.Druhý úkol se používá receive funkce čekat tři sekundy zpráva ze stejné vyrovnávací paměť pro zprávy.receive Vyvolá funkci operation_timed_out pokud neobdrží zprávu v časovém období.

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

Tento příklad vytvoří následující výstup.

  

Chcete-li zabránit abnormálního ukončení aplikace, ujistěte se, že váš kód zpracovává výjimky při volání do modulu runtime.Také zpracujte výjimky při volání do externí kód, který používá modul Runtime souběžnosti, například v knihovně třetí strany.

[Nahoře]

Více výjimek

Pokud úkol nebo paralelní algoritmus přijímá více výjimek, zařazuje pouze jeden z těchto výjimek kontext volání modulu runtime.Modul runtime výjimky, které jej zařazuje nezaručuje.

V následujícím příkladu parallel_for algoritmus pro tisk čísel do konzoly.To vyvolá výjimku, pokud je vstupní hodnota nebo menší než minimální hodnotu větší než maximální hodnotu.V tomto příkladu funkce více práce může vyvolat výjimku.

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

Následuje příklad výstupu pro tento příklad.

  

[Nahoře]

Zrušení

Ne všechny výjimky udávající chybu.Například zastavit jeho přidruženého úkolu, pokud nalezne výsledek algoritmu hledání použít zpracování výjimek.Další informace o použití zrušení mechanismů v kódu naleznete v tématu Zrušení v knihovně PPL.

[Nahoře]

Prosté úlohy

Lehký úkol je úkol, který můžete naplánovat přímo concurrency::Scheduler objektu.Lehké úkoly provádět menší režii než běžné úkony.Modul runtime však výjimky, které jsou vyvolány lehké úkoly nezachytí.Místo toho výjimka zachycena neošetřené výjimky obslužné rutiny, které ukončí proces ve výchozím nastavení.Proto používejte vhodný mechanismus zpracování chyb v aplikaci.Další informace o úlohách, lehký, viz Plánovač úloh (Concurrency Runtime).

[Nahoře]

Asynchronní agenti

Stejně jako lehké úkoly nespravuje modul runtime výjimky, které jsou vyvolány asynchronní agenty.

Následující příklad ukazuje jeden způsob zpracování výjimek ve třídě, která je odvozena z concurrency::agent.Tento příklad definuje points_agent třídy.points_agent::run Metoda čtení point objekty z vyrovnávací paměť pro zprávy a vytisknou se do konzoly.run Metoda vyvolá výjimku, pokud se dostane NULL ukazatele.

run Metoda ohraničuje všechny práce v try-catch blok.catch Blok výjimku ukládá do vyrovnávací paměti zprávy.Aplikace zkontroluje, zda agenta došlo k chybě ve čtení z této vyrovnávací paměti po ukončení 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;
}

Tento příklad vytvoří následující výstup.

  

Protože try-catch bloku existuje mimo while smyčky, agent ukončí zpracování při výskytu první chyby.Pokud try-catch blok byl uvnitř while smyčky, agent by pokračovat poté, co dojde k chybě.

V tomto příkladu ukládá výjimky ve vyrovnávací paměti zprávy jiné komponenty můžete sledovat agenta chyby při jeho spuštění.V tomto příkladu concurrency::single_assignment objektu k ukládání došlo k chybě.V případě, kdy agent zpracovává více výjimek single_assignment třída uchovává pouze první zpráva předaná k němu.Pokud chcete uložit pouze poslední výjimky, použijte concurrency::overwrite_buffer třídy.Chcete-li uložit všechny výjimky, použijte concurrency::unbounded_buffer třídy.Další informace o těchto bloků zprávy naleznete v tématu Asynchronní bloky zpráv.

Další informace o asynchronní agenti, viz Asynchronní agenti.

[Nahoře]

SHRNUTÍ

[Nahoře]

Viz také

Koncepty

Concurrency Runtime

Funkční paralelismus (Concurrency Runtime)

Paralelní algoritmy

Zrušení v knihovně PPL

Plánovač úloh (Concurrency Runtime)

Asynchronní agenti