Převod výjimek mezi vlákny
Jazyk Visual C++ podporuje přenos výjimek z jednoho vlákna do druhého.Přenos výjimek umožňuje zachytit výjimku v jednom vlákně a následně nechat výjimku vypadat, jako by byla vyvolána v jiném vlákně.Například je možné tuto funkci použít pro napsání vícevláknové aplikace, kde primární vlákno zpracovává všechny výjimky vyvolané sekundárními vlákny.Přenos výjimek je užitečný především pro vývojáře, kteří vytvářejí paralelní programovací knihovny nebo systémy.Pro implementaci přenosu výjimek poskytuje jazyk Visual C++ typ exception_ptr a funkce current_exception, rethrow_exception a make_exception_ptr.
namespace std
{
typedef unspecified exception_ptr;
exception_ptr current_exception();
void rethrow_exception(exception_ptr p);
template<class E>
exception_ptr make_exception_ptr(E e) noexcept;
}
Parametry
Parametr |
Popis |
---|---|
unspecified |
Neurčená vnitřní třída, která je použita k implementaci typu exception_ptr. |
p |
Objekt exception_ptr, který odkazuje na výjimku. |
E |
Třída, která představuje výjimku. |
e |
Instance třídy parametru E. |
Vrácená hodnota
Funkce current_exception vrací objekt exception_ptr, který odkazuje na právě probíhající výjimku.Pokud žádná výjimka neprobíhá, vrací funkce objekt exception_ptr, který není přidružen k žádné výjimce.
Funkce make_exception_ptr vrací objekt exception_ptr, který odkazuje na výjimku zadanou parametrem e.
Poznámky
Scénář
Představte si, že je třeba vytvořit aplikaci, která může škálovat pro zpracování různého množství práce.K dosažení tohoto cíle je možné navrhnout vícevláknovou aplikaci, kde původní primární vlákno vytvoří tolik sekundárních vláken, kolik jich je pro práci potřebných.Sekundární vlákna pomohou primárnímu vláknu spravovat prostředky, vyvažovat zatížení a zvýšit propustnost.Díky distribuci práce poskytuje vícevláknová aplikace lepší výkon než jednovláknová aplikace.
Avšak pokud sekundární vlákno vyvolá výjimku, mělo by ji primární vlákno zpracovat.To proto, aby aplikace zpracovávala výjimky konzistentním způsobem bez ohledu na počet sekundárních vláken.
Řešení
Pro zvládnutí předchozího scénáře podporuje standard jazyka C++ přenos výjimek mezi vlákny.Pokud sekundární vlákno vyvolá výjimku, tato výjimka se stane aktuální výjimkou.Podle analogie reálné situace je aktuální výjimka označována jako v letu.Aktuální výjimka je v letu od okamžiku jejího vyvolání po návrat obslužné rutiny výjimky, která ji zachytila.
Sekundární vlákno může zachytit aktuální výjimku v bloku catch a pak zavolat funkci current_exception pro uložení výjimky do objektu exception_ptr.Objekt exception_ptr musí být k dispozici jak sekundárnímu vláknu, tak primárnímu vláknu.Například objekt exception_ptr může být globální proměnnou, jejíž přístup je řízen mutexem.Termín přenést výjimku znamená, že výjimka z jednoho vlákna může být převedena do tvaru, který je přístupný jinému vláknu.
Dále primární vlákno volá funkci rethrow_exception, která z objektu exception_ptr extrahuje a následně vyvolává výjimku.Jakmile je výjimka vyvolána, stane se v primárním vlákně aktuální výjimkou.A tak se zdá, že výjimka pochází z primárního vlákna.
Nakonec může primární vlákno zachytit aktuální výjimku v bloku catch a poté ji zpracovat nebo ji vyvolat pro obslužnou rutinu vyšší úrovně.Nebo může primární vlákno výjimku ignorovat a dovolit procesu skončit.
Většina aplikací nepotřebuje přenášet výjimky mezi vlákny.Nicméně tato funkce je užitečná v paralelních výpočetních systémech, protože systém může rozdělit práci mezi sekundární vlákna, procesory nebo jádra.V prostředí paralelních výpočtů může jedno vyhrazené vlákno zpracovávat všechny výjimky ze sekundárních vláken a poskytnout konzistentní model zpracování výjimek pro jakoukoli aplikaci.
Pro další informace o návrhu komise standardu jazyka C++ vyhledejte na internetu dokument s číslem N2179 a názvem „Language Support for Transporting Exceptions between Threads“.
Modely zpracování výjimek a možnosti kompilátoru
Model zpracování výjimek v aplikaci určuje, zda lze zachytit a přenést výjimku.Jazyk Visual C++ podporuje tři modely, kterými lze zpracovávat výjimky jazyka C++, výjimky strukturovaného zpracování výjimek (SEH) a výjimky modulu CLR.Pro určení modelu zpracování výjimek je třeba použít možnosti kompilátoru /EH a /CLR.
Pouze následující kombinace možností kompilátoru a programovacích příkazů může přenést výjimku.Další kombinace buď nemohou zachytit výjimky, nebo je mohou zachytit, ale nemohou přenést.
Možnost kompilátoru /EHa a příkaz catch mohou přenášet SEH a výjimky jazyka C++.
Možnosti kompilátoru /EHa, /EHs a /EHsc a příkaz catch mohou přenášet výjimky jazyka C++.
Možnosti kompilátoru /CLR nebo /CLR:pure a příkaz catch mohou přenášet výjimky jazyka C++.Možnost kompilátoru /CLR implikuje zadání možnosti /EHa.Je dobré si zapamatovat, že kompilátor nepodporuje přenos spravovaných výjimek.Důvodem je, že spravované výjimky, které jsou odvozeny z třídy System.Exception, již jsou objekty, které lze přesunout mezi vlákny pomocí prostředků modulu CLR.
Poznámka k zabezpečení
Je doporučeno zadat možnost kompilátoru /EHsc a zachycovat pouze výjimky jazyka C++.Použitím možnosti kompilátoru /EHa nebo /CLR a příkazu catch se třemi tečkami místo exception-declaration (catch(...)) se vystavíte bezpečnostnímu riziku.Příkaz catch by měl být použit pro zachycení několika specifických výjimek.Nicméně příkaz catch(...) zachytí všechny výjimky jazyka C++ a SEH, včetně neočekávaných, které mohou být závažné.Ignorováním nebo nesprávným zpracováním neočekávané výjimky může být dán prostor škodlivému kódu k ohrožení zabezpečení aplikace.
Použití
Následující části popisují způsob přenášení výjimky pomocí typu exception_ptr a funkcí current_exception, rethrow_exception a make_exception_ptr.
Typ exception_ptr
Použijte objekt exception_ptr pro odkázání na aktuální výjimku nebo instanci uživatelské výjimky.V implementaci společnosti Microsoft je výjimka představována strukturou EXCEPTION_RECORD.Každý objekt exception_ptr obsahuje referenční pole výjimky, které odkazuje na kopii struktury EXCEPTION_RECORD představující výjimku.
Při deklaraci proměnné exception_ptr není proměnná přidružena k žádné výjimce.Tedy referenční pole výjimky je NULL.Takovýto objekt exception_ptr se nazývá nulový exception_ptr.
Pro přiřazení výjimky objektu exception_ptr je třeba použít funkci current_exception nebo make_exception_ptr.Při přiřazení výjimky do proměnné exception_ptr bude referenční pole výjimky proměnné odkazovat na kopii výjimky.Pokud není dostatek paměti pro zkopírování výjimky, referenční pole výjimky odkazuje na kopii výjimky std::bad_alloc.Pokud funkce current_exception nebo make_exception_ptr nemůže zkopírovat výjimku z jiného důvodu, zavolá funkce funkci terminate (CRT), aby ukončila aktuální proces.
Navzdory názvu není objekt exception_ptr sám o sobě ukazatel.Nedodržuje sémantiku ukazatele a nelze jej použít s operátory přístupu k členovi (->) nebo dereference (*).Objekt exception_ptr nemá žádné veřejné datové členy a členské funkce.
Porovnání:
Pro porovnání dvou objektů exception_ptr je možné použít operátory je rovno (==) a není rovno (!=).Operátory neporovnávají binární hodnoty (bitový vzor) struktur EXCEPTION_RECORD představujících výjimky.Místo toho operátory porovnávají adresy v referenčním poli výjimky objektu exception_ptr.V důsledku toho se nulový exception_ptr a hodnota NULL rovnají.
Funkce current_exception
Volejte funkci current_exception v zachytávacím bloku catch.Je-li výjimka v letu a blok catch může výjimku zachytit, funkce current_exception vrátí objekt exception_ptr, který na výjimku odkazuje.V ostatních případech vrátí funkce objekt exception_ptr s hodnotou null.
Podrobnosti:
Funkce current_exception zachytí výjimku, která je v letu, bez ohledu na to, zda příkaz catch určuje příkaz exception-declaration.
Destruktor aktuální výjimky je volán na konci bloku catch, pokud není výjimka znovu vyvolána.Avšak i v případě volání funkce current_exception v destruktoru vrátí funkce objekt exception_ptr, který odkazuje na aktuální výjimku.
Následná volání funkce current_exception vrací objekty exception_ptr, které odkazují na různé kopie aktuální výjimky.V důsledku toho se objekty při porovnání jeví jako nerovné, protože odkazují na jiné kopie, i přesto, že kopie mají stejné binární hodnoty.
Výjimky SEH:
Při použití možnosti kompilátoru /EHa je možné zachytit výjimku SEH v bloku catch jazyka C++.Funkce current_exception vrátí objekt exception_ptr, který odkazuje na výjimku SEH.Funkce rethrow_exception vyvolá výjimku SEH při volání spřeneseným objektem exception_ptr jako argumentem.
Funkce current_exception vrátí nulový exception_ptr při volání v ukončovací rutině SEH __finally, obslužné rutině výjimky __except nebo výrazu filtru __except.
Přenesená výjimka nepodporuje vnořené výjimky.Vnořená výjimka nastane, pokud je při zpracování výjimky vyvolána jiná výjimka.Při zachycení vnořené výjimky datový člen EXCEPTION_RECORD.ExceptionRecord odkazuje na řetězec struktur EXCEPTION_RECORD, které popisují přidružené výjimky.Funkce current_exception nepodporuje vnořené výjimky, protože vrací objekt exception_ptr, jehož datový člen ExceptionRecord je vynulován.
Při zachycení výjimky SEH je nutné spravovat paměť odkazovanou ukazateli v poli datového členu EXCEPTION_RECORD.ExceptionInformation.Je nutné zaručit, že paměť je platná po dobu životnosti odpovídajícího objektu exception_ptr a že je paměť uvolněna při odstranění objektu exception_ptr.
Spolu s možností přenosu výjimek je možné použít funkce překladatele strukturovaných výjimek (SE).Pokud je výjimka SEH přeložena do výjimky jazyka C++, funkce current_exception vrátí exception_ptr odkazující na přeloženou výjimku namísto původní výjimky SEH.Funkce rethrow_exception následně vyvolá přeloženou výjimku, nikoli původní výjimku.Další informace o funkcích překladače SE naleznete v tématu _set_se_translator.
Funkce rethrow_exception
Po uložení zachycené výjimky v objektu exception_ptr může primární vlákno objekt zpracovat.V primárním vlákně je třeba zavolat funkci rethrow_exception společně s objektem exception_ptr jako argumentem.Funkce rethrow_exception extrahuje výjimku z objektu exception_ptr a potom vyvolá výjimku v kontextu primárního vlákna.Pokud je parametr p funkce rethrow_exception nulový exception_ptr, funkce vyvolá std::bad_exception.
Extrahovaná výjimka je nyní aktuální výjimkou v primárním vlákně a je možné ji zpracovávat stejně jako jakoukoli jinou výjimku.Při zachycení výjimky je možné ji okamžitě zpracovat nebo použít příkaz throw k odeslání obslužné rutině vyšší úrovně.Jinak není třeba dělat nic a je možné nechat výchozí obslužnou rutinu systému ukončit proces.
Funkce make_exception_ptr
Funkce make_exception_ptr přebírá jako argument instanci třídy a vrací exception_ptr odkazující na tuto instanci.Obvykle jako argument funkce make_exception_ptr určíte objekt třída výjimky, ačkoli může být argumentem jakýkoli objekt třídy.
Volání funkce make_exception_ptr je ekvivalentní k vyvolání výjimky jazyka C++, jejímu zachycení v bloku catch a následným voláním funkce current_exception pro vrácení objektu exception_ptr odkazujícího na výjimku.Implementace funkce make_exception_ptr společnosti Microsoft je mnohem efektivnější než vyvolávání a následné zachycování výjimky.
Aplikace obvykle nevyžaduje funkci make_exception_ptr a její použití není doporučeno.
Příklad
V následujícím příkladu je standardní výjimka jazyka C++ a vlastní výjimka jazyka C++ přenesena z jednoho vlákna do druhého.
// transport_exception.cpp
// compile with: /EHsc /MD
#include <windows.h>
#include <stdio.h>
#include <exception>
#include <stdexcept>
using namespace std;
// Define thread-specific information.
#define THREADCOUNT 2
exception_ptr aException[THREADCOUNT];
int aArg[THREADCOUNT];
DWORD WINAPI ThrowExceptions( LPVOID );
// Specify a user-defined, custom exception.
// As a best practice, derive your exception
// directly or indirectly from std::exception.
class myException : public std::exception {
};
int main()
{
HANDLE aThread[THREADCOUNT];
DWORD ThreadID;
// Create secondary threads.
for( int i=0; i < THREADCOUNT; i++ )
{
aArg[i] = i;
aThread[i] = CreateThread(
NULL, // Default security attributes.
0, // Default stack size.
(LPTHREAD_START_ROUTINE) ThrowExceptions,
(LPVOID) &aArg[i], // Thread function argument.
0, // Default creation flags.
&ThreadID); // Receives thread identifier.
if( aThread[i] == NULL )
{
printf("CreateThread error: %d\n", GetLastError());
return -1;
}
}
// Wait for all threads to terminate.
WaitForMultipleObjects(THREADCOUNT, aThread, TRUE, INFINITE);
// Close thread handles.
for( int i=0; i < THREADCOUNT; i++ ) {
CloseHandle(aThread[i]);
}
// Rethrow and catch the transported exceptions.
for ( int i = 0; i < THREADCOUNT; i++ ) {
try {
if (aException[i] == NULL) {
printf("exception_ptr %d: No exception was transported.\n", i);
}
else {
rethrow_exception( aException[i] );
}
}
catch( const invalid_argument & ) {
printf("exception_ptr %d: Caught an invalid_argument exception.\n", i);
}
catch( const myException & ) {
printf("exception_ptr %d: Caught a myException exception.\n", i);
}
}
}
// Each thread throws an exception depending on its thread
// function argument, and then ends.
DWORD WINAPI ThrowExceptions( LPVOID lpParam )
{
int x = *((int*)lpParam);
if (x == 0) {
try {
// Standard C++ exception.
// This example explicitly throws invalid_argument exception.
// In practice, your application performs an operation that
// implicitly throws an exception.
throw invalid_argument("A C++ exception.");
}
catch ( const invalid_argument & ) {
aException[x] = current_exception();
}
}
else {
// User-defined exception.
aException[x] = make_exception_ptr( myException() );
}
return TRUE;
}
Požadavky
Hlavička: <exception>
Viz také
Referenční dokumentace
Zpracování výjimek v jazyce Visual C++
Struktura EXCEPTION_RECORD
Syntaxe obslužné rutiny