Sdílet prostřednictvím


Zpracování výjimek v Concurrency Runtime

Concurrency Runtime používá zpracování výjimek jazyka C++ ke komunikaci mnoha druhů chyb. Mezi tyto chyby patří neplatné použití modulu runtime, chyby modulu runtime, jako je selhání získání prostředku, a chyby, ke kterým dochází v pracovních funkcích, které zadáte úkolům a skupinám úloh. Když úloha nebo skupina úloh vyvolá výjimku, modul runtime ji uloží a zařadí ji do kontextu, který čeká na dokončení úkolu nebo skupiny úloh. U komponent, jako jsou zjednodušené úlohy a agenti, modul runtime za vás nespravuje výjimky. V těchto případech musíte implementovat vlastní mechanismus zpracování výjimek. Toto téma popisuje, jak modul runtime zpracovává výjimky vyvolané úlohami, skupinami úloh, jednoduchými úlohami a asynchronními agenty a jak reagovat na výjimky ve vašich aplikacích.

Klíčové body

  • Když úloha nebo skupina úloh vyvolá výjimku, modul runtime ji uloží a zařadí ji do kontextu, který čeká na dokončení úkolu nebo skupiny úloh.

  • Pokud je to možné, ohraničujte každé volání souběžnosti::task::get a concurrency::task::wait s blokem try/catch pro zpracování chyb, ze kterého se můžete zotavit. Modul runtime ukončí aplikaci, pokud úloha vyvolá výjimku a tato výjimka není zachycena úlohou, jedním z jeho pokračování nebo hlavní aplikací.

  • Pokračování založené na úlohách se vždy spustí; nezáleží na tom, jestli se úloha typutecedent úspěšně dokončila, vyvolala výjimku nebo byla zrušena. Pokračování založené na hodnotách se nespustí, pokud úloha typu antecedent vyvolá nebo zruší.

  • Vzhledem k tomu, že pokračování založená na úlohách se vždy spouštějí, zvažte, jestli chcete na konec řetězu pokračování přidat pokračování na základě úlohy. To může pomoct zaručit, že váš kód sleduje všechny výjimky.

  • Modul runtime vyvolá souběžnost::task_canceled při volání souběžnosti::task::get a úloha se zruší.

  • Modul runtime nespravuje výjimky pro zjednodušené úlohy a agenty.

V tomto dokumentu

Úkoly a pokračování

Tato část popisuje, jak modul runtime zpracovává výjimky vyvolané souběžností::task objekty a jejich pokračováními. Další informace o modelu úloh a pokračování naleznete v tématu Paralelismus úloh.

Když vyvoláte výjimku v těle pracovní funkce, kterou předáte objektu task , modul runtime uloží tuto výjimku a zařadí ji do kontextu, který volá souběžnost::task::get nebo concurrency::task::wait. Dokument Úkol paralelismus popisuje pokračování na základě úkolů a hodnot, ale pro shrnutí pokračování na základě hodnoty přebírá parametr typu T a pokračování založené na úkolu přebírá parametr typu task<T>. Pokud úloha, která vyvolá, má jedno nebo více pokračování založených na hodnotách, nejsou tato pokračování naplánována ke spuštění. Následující příklad ukazuje toto chování:

// 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í založené na úlohách umožňuje zpracovat všechny výjimky, které vyvolá úloha typu antecedent. Pokračování založené na úlohách se vždy spustí; nezáleží na tom, jestli se úloha úspěšně dokončila, vyvolala výjimku nebo byla zrušena. Když úloha vyvolá výjimku, její pokračování na základě úloh se naplánují tak, aby se spustily. Následující příklad ukazuje úkol, který vždy vyvolá. Úkol má dvě pokračování; jedna je založená na hodnotách a druhá je založená na úkolech. Výjimka založená na úlohách se vždy spustí, a proto může zachytit výjimku, která je vyvolána úlohou antecedent. Když příklad čeká na dokončení obou pokračování, je výjimka vyvolána znovu, protože výjimka úkolu je vždy vyvolána při task::get nebo task::wait je volána.

// 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 pokračování založená na úlohách k zachycení výjimek, které můžete zpracovat. Vzhledem k tomu, že pokračování založená na úlohách se vždy spouštějí, zvažte, jestli chcete na konec řetězu pokračování přidat pokračování na základě úlohy. To může pomoct zaručit, že váš kód sleduje všechny výjimky. Následující příklad ukazuje základní řetězec pokračování založený na hodnotách. Třetí úkol v řetězu vyvolá, a proto všechny pokračování založené na hodnotě, které následují, nejsou spuštěny. Poslední pokračování je však založené na úlohách, a proto se vždy spustí. Toto konečné pokračování zpracovává výjimku, která je vyvolán třetí úlohou.

Doporučujeme zachytit nejvýraznější výjimky, které můžete použít. Pokud nemáte konkrétní výjimky, které se mají zachytit, můžete tuto konečnou pokračování na základě úloh vynechat. Všechny výjimky zůstanou neošetřené a můžou aplikaci ukončit.

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

Tip

K přidružení výjimky k události dokončení úkolu můžete použít metodu concurrency::task_completion_event::set_exception . Dokument Task Parallelism popisuje concurrency::task_completion_event třídu podrobněji.

concurrency::task_canceled je důležitý typ výjimky modulu runtime, který souvisí s task. Modul runtime se vyvolá task_canceled při volání task::get a úloha se zruší. (Naopak task::wait vrátí task_status::canceled a nevyvolá se.) Tuto výjimku můžete zachytit a zpracovat z pokračování založeného na úkolu nebo při volání task::get. Další informace o zrušení úkolu naleznete v tématu Zrušení v PPL.

Upozornění

Nikdy z kódu nevyhazujte task_canceled . Místo toho zavolejte concurrency::cancel_current_task .

Modul runtime ukončí aplikaci, pokud úloha vyvolá výjimku a tato výjimka není zachycena úlohou, jedním z jeho pokračování nebo hlavní aplikací. Pokud dojde k chybovému ukončení aplikace, můžete sadu Visual Studio nakonfigurovat tak, aby se přerušovala, když dojde k vyvolání výjimek jazyka C++. Po diagnostice umístění neošetřené výjimky použijte k jeho zpracování pokračování na základě úlohy.

Oddíl Výjimky vyvolané modulem runtime v tomto dokumentu popisuje, jak pracovat s výjimkami 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 skupinami úloh. Tato část platí také pro paralelní algoritmy, jako je souběžnost::p arallel_for, protože tyto algoritmy vycházejí ze skupin úloh.

Upozornění

Ujistěte se, že rozumíte účinkům, které výjimky mají na závislých úkolech. Doporučené postupy pro používání zpracování výjimek s úlohami nebo paralelními algoritmy najdete v části Vysvětlení toho, jak zrušení a zpracování výjimek ovlivňuje zničení objektů v části Osvědčené postupy v tématu Knihovny paralelních vzorů.

Další informace o skupinách úloh naleznete v tématu Paralelismus úkolu. Další informace o paralelních algoritmech naleznete v tématu Paralelní algoritmy.

Když v těle pracovní funkce vyvoláte výjimku, kterou předáte souběžnosti::task_group nebo concurrency::structured_task_group objektu, modul runtime uloží tuto výjimku a zařadí ji do kontextu, který volá souběžnost::task_group::wait, concurrency::structured_task_group::wait, concurrency::task_group::run_and_wait nebo souběžnost:: structured_task_group::run_and_wait. Modul runtime také zastaví všechny aktivní úkoly, které jsou ve skupině úloh (včetně těch v podřízených skupinách úkolů) a zahodí všechny úkoly, které ještě nebyly spuštěny.

Následující příklad ukazuje základní strukturu pracovní funkce, která vyvolá výjimku. Příklad používá task_group objekt k paralelnímu tisku hodnot dvou point objektů. Pracovní print_point funkce vytiskne hodnoty point objektu do konzoly. Funkce práce vyvolá výjimku, pokud je NULLvstupní hodnota . Modul runtime uloží tuto výjimku a zařadí ji 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.

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

Úplný příklad, který používá zpracování výjimek ve skupině úloh, naleznete v tématu Postupy: Použití zpracování výjimek k přerušení paralelní smyčky.

[Nahoře]

Výjimky vyvolané modulem runtime

Výjimka může být výsledkem volání modulu runtime. Většina typů výjimek s výjimkou souběžnosti::task_canceled a souběžnosti::operation_timed_out označuje chybu programování. Tyto chyby jsou obvykle nedostupné, a proto by se neměly zachytit ani zpracovat kódem aplikace. Doporučujeme zachytit nebo zpracovat neopravitelné chyby v kódu aplikace, když potřebujete diagnostikovat chyby programování. Pochopení typů výjimek definovaných modulem runtime vám ale může pomoct s diagnostikou chyb programování.

Mechanismus zpracování výjimek je stejný pro výjimky, které jsou vyvolány modulem runtime jako výjimky vyvolané pracovními funkcemi. Například souběžnost::receive funkce vyvolá operation_timed_out , když neobdrží zprávu v zadaném časovém období. Pokud receive vyvolá výjimku v pracovní funkci, kterou předáte skupině úloh, modul runtime uloží tuto výjimku a zařadí ji do kontextu, který volá task_group::wait, structured_task_group::wait, task_group::run_and_waitnebo structured_task_group::run_and_wait.

Následující příklad používá algoritmus concurrency::p arallel_invoke k paralelnímu spuštění dvou úloh. První úkol počká pět sekund a pak odešle zprávu do vyrovnávací paměti zprávy. Druhý úkol používá receive funkci k čekání na přijetí zprávy ze stejné vyrovnávací paměti zprávy tři sekundy. Funkce receive vyvolá operation_timed_out zprávu v případě, že zprávu neobdrží 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.

The operation timed out.

Pokud chcete zabránit neobvyklému ukončení aplikace, ujistěte se, že kód zpracovává výjimky při volání do modulu runtime. Při volání do externího kódu, který používá Concurrency Runtime, například knihovnu třetí strany, také zpracujte výjimky.

[Nahoře]

Více výjimek

Pokud úloha nebo paralelní algoritmus obdrží více výjimek, zařadí modul runtime pouze jednu z těchto výjimek do kontextu volání. Modul runtime nezaručuje, kterou výjimku zařazuje.

Následující příklad používá parallel_for algoritmus k tisku čísel do konzoly. Vyvolá výjimku, pokud je vstupní hodnota menší než minimální hodnota nebo větší než určitá maximální hodnota. V tomto příkladu může vyvolat výjimku více pracovních funkcí.

// 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ásledující příklad ukazuje ukázkový výstup.

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

[Nahoře]

Zrušení

Ne všechny výjimky značí chybu. Například vyhledávací algoritmus může při hledání výsledku zastavit související úlohu pomocí zpracování výjimek. Další informace o tom, jak používat mechanismy zrušení v kódu, naleznete v tématu Zrušení v PPL.

[Nahoře]

Prosté úlohy

Jednoduchý úkol je úkol, který plánujete přímo z objektu concurrency::Scheduler . Zjednodušené úkoly mají menší režii než běžné úkoly. Modul runtime však nezachytí výjimky, které jsou vyvolány jednoduchými úlohami. Místo toho je výjimka zachycena neošetřenou obslužnou rutinou výjimky, která ve výchozím nastavení proces ukončí. Proto ve vaší aplikaci použijte vhodný mechanismus zpracování chyb. Další informace o jednoduchých úkolech naleznete v tématu Plánovač úloh.

[Nahoře]

Asynchronní agenti

Podobně jako u jednoduchých úloh modul runtime nespravuje výjimky, které jsou vyvolány asynchronními agenty.

Následující příklad ukazuje jeden ze způsobů, jak zpracovat výjimky ve třídě, která je odvozena z concurrency::agent. Tento příklad definuje points_agent třídu. Metoda points_agent::run čte point objekty z vyrovnávací paměti zprávy a vytiskne je do konzoly. Metoda run vyvolá výjimku, pokud obdrží NULL ukazatel.

Metoda run obklopuje všechny práce v try-catch bloku. Blok catch ukládá výjimku do vyrovnávací paměti zprávy. Aplikace zkontroluje, jestli agent po dokončení agenta zjistil chybu čtením z této vyrovnávací paměti.

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

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

Vzhledem k tomu, že try-catch blok existuje mimo smyčku while , agent ukončí zpracování, když dojde k první chybě. trycatch-Pokud byl blok uvnitř while smyčky, agent bude pokračovat po chybě.

Tento příklad ukládá výjimky do vyrovnávací paměti zpráv, aby jiná komponenta mohl monitorovat chyby agenta při spuštění. V tomto příkladu se k uložení chyby používá souběžnost::single_assignment objekt. V případě, že agent zpracovává více výjimek, single_assignment třída ukládá pouze první zprávu, která je předána do ní. Pokud chcete uložit pouze poslední výjimku, použijte třídu concurrency::overwrite_buffer . Pokud chcete uložit všechny výjimky, použijte třídu concurrency::unbounded_buffer . Další informace o těchto blocích zpráv naleznete v tématu Asynchronní bloky zpráv.

Další informace o asynchronních agentech naleznete v tématu Asynchronní agenti.

[Nahoře]

Shrnutí

[Nahoře]

Viz také

Concurrency Runtime
Paralelismus úkolu
Paralelní algoritmy
Zrušení v knihovně PPL
Plánovač úloh
Asynchronní agenti