transport wyjątków między wątkami
Visual C++ obsługuje transport wyjątku z jednego wątku do innego.Transport wyjątków umożliwia przechwytywanie wyjątków w jednym wątku, a następnie powodowanie, aby były generowane w innym wątku.Na przykład, możesz użyć tej funkcji do pisania aplikacji wielowątkowych, gdzie wątek główny obsługuje wszystkie wyjątki generowane przez pomocnicze wątki.Transport wyjątków jest przydatny głównie dla deweloperów, którzy tworzą biblioteki lub systemy programowania równoległego.Aby zaimplementować transport wyjątków, Visual C++ dostarcza typ exception_ptr oraz funkcje current_exception, rethrow_exception i 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 |
Opis |
---|---|
unspecified |
Nieokreślona klasa wewnętrzna, która jest używana do zaimplementowania typu exception_ptr. |
p |
Obiekt exception_ptr, który odwołuje się do wyjątku. |
E |
Klasa, która reprezentuje wyjątek. |
e |
Wystąpienie parametru klasy E. |
Wartość zwracana
Funkcja current_exception zwraca obiekt exception_ptr odwołujący się do wyjątku, który jest obecnie w toku.Jeśli nie ma wyjątków w toku, funkcja zwraca obiekt exception_ptr, który nie jest skojarzony z żadnym wyjątkiem.
Funkcja make_exception_ptr zwraca obiekt exception_ptr, który odwołuje się do wyjątków określonych przez parametr e.
Uwagi
Scenariusz
Wyobraź sobie, że chcesz utworzyć aplikację, która może być skalowana, aby obsłużyć zmienną ilość pracy.Aby osiągnąć ten cel, projektujesz aplikację wielowątkową, gdzie wstępny, podstawowy wątek tworzy tyle wątków pomocniczych, ile potrzebuje, aby wykonać zadanie.Pomocnicze wątki pomagają wątkowi głównemu zarządzać zasobami, równoważyć obciążenia, a także zwiększać wydajność.Dzięki rozłożeniu pracy, aplikacja wielowątkowa działa lepiej niż aplikacja jednowątkowa.
Jednak jeśli wątek pomocniczy zgłasza wyjątek, wskazane jest, aby watek główny go obsłużył.To dlatego, że chcesz, aby aplikacja obsługiwała wyjątki w sposób spójny, jednolity, bez względu na liczbę wątków pomocniczych.
Rozwiązanie
Aby obsłużyć poprzedni scenariusz, standard C++ obsługuje transportowanie wyjątków pomiędzy wątkami.Jeśli wątek pomocniczy zgłasza wyjątek, wyjątek ten staje się wyjątkiem bieżącym.Poprzez analogię do świata rzeczywistego, mówi się, że wyjątek bieżący jest w locie.Bieżący wyjątek jest w locie od czasu, kiedy jest generowany, aż do momentu, gdy zostanie zwrócony przez kod obsługi wyjątków, który go przechwycił.
Wątek pomocniczy może przechwycić wyjątek bieżący w bloku catch, a następnie wywołać funkcję current_exception do przechowywania wyjątku w obiekcie exception_ptr.Obiekt exception_ptr musi być dostępny dla pomocniczego wątku i wątku głównego.Na przykład obiekt exception_ptr może być zmienną globalną, do której dostęp jest kontrolowany przez mutex.Termin transport wyjątku oznacza, że wyjątek w jednym wątku można przekonwertować na formę, do której ma dostęp inny wątek.
Następnie wątek główny wywołuje funkcję rethrow_exception, która wyodrębnia i następnie zgłasza wyjątek z obiektu exception_ptr.Wygenerowany wyjątek staje się bieżącym wyjątkiem w wątku głównym.Oznacza to, że wyjątek wygląda tak, jakby pochodził z wątku głównego.
Wreszcie, wątek główny może przechwycić bieżący wyjątek w bloku catch, a następnie go przetworzyć lub zgłosić do wyższego poziomu obsługi wyjątków.Lub wątek główny może zignorować wyjątek i pozwolić zakończyć proces.
Większość aplikacji nie musi transportować wyjątków między wątkami.Jednak ta funkcja jest przydatna w systemach przetwarzania równoległego, ponieważ system może podzielić pracę pomiędzy wątki pomocnicze, procesory lub rdzenie.W środowisku przetwarzania równoległego pojedynczy, wyspecjalizowany wątek może obsługiwać wszystkie wyjątki z pomocniczych wątków i może stanowić spójny model obsługi wyjątków dla innych aplikacji.
Aby uzyskać więcej informacji o propozycji komitetu standardów C++, znajdź w Internecie dokument o numerze N2179 pod tytułem „Language Support for Transporting Exceptions between Threads” (Obsługa języka dla transportu wyjątków między wątkami).
Opcje kompilatora i modele obsługi wyjątków
Model obsługi wyjątków aplikacji określa, czy można przechwycić i transportować wyjątek.Visual C++ obsługuje trzy modele, które mogą obsługiwać wyjątki C++, obsługę wyjątków strukturalnych (SEH) i wyjątki środowiska uruchomieniowego języka wspólnego (CLR).Użyj opcji kompilatora /EH i /clr, aby określić model obsługi wyjątków aplikacji.
Tylko następujące połączenie opcji kompilatora i instrukcji programowania może transportować wyjątek.Inne połączenia nie mogą przechwytywać wyjątków lub mogą je przechwytywać, ale nie transportować.
Opcja kompilatora /EHa i instrukcja catch transportuje wyjątki SEH i C++.
Opcje kompilatora /EHa, /EHs, /EHsc oraz instrukcja catch mogą transportować wyjątki C++.
Opcja kompilatora /CLR lub /CLR:pure i instrukcja catch mogą transportować wyjątki C++.Opcje kompilatora /CLR implikują podanie opcji /EHa.Należy zauważyć, że kompilator nie obsługuje transportowania wyjątków zarządzanych.To dlatego, że wyjątki zarządzane, które są wyprowadzane z klasy System.Exception, są już obiektami, które mogą być przenoszone między wątkami przy użyciu funkcji środowiska uruchomieniowego języka wspólnego.
Uwaga dotycząca zabezpieczeń Firma Microsoft zaleca, aby określić opcję kompilatora /EHsc i przechwytywać tylko wyjątki C++.Można się narazić na zagrożenia bezpieczeństwa, korzystając z opcji kompilatora /EHa lub /CLR i instrukcji catch z wielokropkiem zgłoszenie wyjątku (catch(...)).Zamierzasz prawdopodobnie używać instrukcji catch, aby przechwycić kilka szczególnych wyjątków.Jednakże instrukcja catch(...) przechwytuje wszystkie wyjątki C++ i SEH, w tym nieoczekiwane, które powinny być krytyczne.Jeśli zignorujesz lub źle obsłużysz nieoczekiwany wyjątek, złośliwy kod wykorzystać tę szansę do obejścia zabezpieczeń programu.
Użycie
W poniższych sekcjach opisano sposób transportu wyjątków za pomocą typu exception_ptr i funkcji current_exception, rethrow_exception i make_exception_ptr.
Typ exception_ptr
Użyj obiektu exception_ptr, aby odwoływać się do bieżącego wyjątku lub wystąpienia wyjątku określonego przez użytkownika.W implementacji firmy Microsoft wyjątek jest reprezentowany przez strukturę EXCEPTION_RECORD.Każdy obiekt exception_ptr zawiera pole odwołania wyjątku, wskazujące na kopię struktury EXCEPTION_RECORD, która reprezentuje wyjątek.
Po zadeklarowaniu zmiennej exception_ptr, zmienna nie jest skojarzona z żadnym wyjątkiem.To znaczy, że pole odwołania wyjątku ma wartość NULL.Taki obiekt, jak exception_ptr, jest nazywany null exception_ptr.
Użyj funkcji current_exception lub make_exception_ptr, aby przypisać wyjątek do obiektu exception_ptr.Podczas przypisywania wyjątku do zmiennej exception_ptr, pole odwołania do zmiennej wyjątku wskazuje kopię wyjątku.Jeśli pamięć jest niewystarczająca, aby skopiować wyjątek, pole odwołania wyjątku wskazuje kopię wyjątku std::bad_alloc.Jeśli funkcja current_exception lub make_exception_ptr z jakiegokolwiek innego powodu nie może skopiować wyjątku, funkcja wywołuje zakończenie (CRT), aby zakończyć bieżący proces.
Pomimo swojej nazwy obiekt exception_ptr sam nie jest wskaźnikiem.Nie stosuje semantyki wskaźnika i nie można go używać z operatorami dostępu do wskaźnika elementu członkowskiego (->) lub pośrednimi (*).Obiekt exception_ptr nie ma publicznych elementów członkowskich danych ani funkcji elementów członkowskich.
Porównania:
Można użyć operatorów równości (==) i nierówności (!=) do porównywania dwóch obiektów exception_ptr.Operatory nie porównują wartości binarnej (wzorca bitowego) struktur EXCEPTION_RECORD, które reprezentują wyjątki.Zamiast tego, operatory porównują adresy w polu odwołania do wyjątku obiektów exception_ptr.W związku z tym, porównywane wartości null w exception_ptr oraz NULL są równe.
Funkcja current_exception
Wywołaj funkcję current_exception w bloku catch.Jeśli wyjątek jest w locie i blok catch może przechwycić wyjątek, funkcja current_exception zwraca obiekt exception_ptr, który odwołuje się do tego wyjątku.W przeciwnym razie funkcja zwraca wartość null obiektu exception_ptr.
Szczegóły:
Funkcja current_exception przechwytuje wyjątek, który jest w locie, bez względu na to, czy instrukcja catch określa instrukcję zgłoszenie wyjątku.
Destruktor dla bieżącego wyjątku jest wywoływany pod koniec bloku catch, jeśli wyjątek nie jest ponownie zgłaszany.Jednak nawet jeśli wywołasz funkcję current_exception w destruktorze, funkcja zwraca obiekt exception_ptr, który odwołuje się do bieżącego wyjątku.
Kolejne wywołania funkcji current_exception zwracają obiekty exception_ptr, które odnoszą się do różnych kopii bieżącego wyjątku.W związku z tym obiekty są porównane jako nierówne, ponieważ odnoszą się one do poszczególnych kopii, mimo że kopie mają tę samą wartość binarną.
Wyjątki SEH:
Jeśli używasz opcji kompilatora /EHa, możesz przechwytywać wyjątki obsługi wyjątków strukturalnych w bloku C++ catch.Funkcja current_exception zwraca obiekt exception_ptr, który odwołuje się do wyjątku SEH.Także funkcja rethrow_exception zgłasza wyjątek SEH, jeśli wywołujesz ją ztransportowanym obiektem exception_ptr jako jej argumentem.
Funkcja current_exception zwraca wartość null exception_ptr jeśli wywołasz ją w kodzie obsługi zakończenia __finally SEH, kodzie obsługi wyjątków __except lub wyrażeniu filtrującym __except.
Transportowany wyjątek nie obsługuje wyjątków zagnieżdżonych.Wyjątek zagnieżdżony występuje, jeśli podczas obsługi wyjątku jest zgłaszany inny wyjątek.Jeśli przechwycisz wyjątek zagnieżdżony, element członkowski danych EXCEPTION_RECORD.ExceptionRecord wskazuje łańcuch struktur EXCEPTION_RECORD, które opisują wyjątki skojarzone.Funkcja current_exception nie obsługuje wyjątków zagnieżdżonych, ponieważ zwraca obiekt exception_ptr, którego element członkowski danych ExceptionRecord jest zerowany.
Jeśli przechwytujesz wyjątek SEH, musisz zarządzać pamięcią, do której odwołują się wskaźniki z tablicy elementów członkowskich danych EXCEPTION_RECORD.ExceptionInformation.Musisz gwarantować, że pamięć jest prawidłowa w okresie istnienia odpowiadającego obiektu exception_ptr i że pamięć jest zwalniana, gdy obiekt exception_ptr zostaje usunięty.
Można użyć funkcji tłumacza wyjątków strukturalnych (SE) wraz z funkcją transportu wyjątków.Jeśli wyjątek SEH jest tłumaczony na wyjątek C++, funkcja current_exception zwraca exception_ptr odwołujący się do przetłumaczonego wyjątku zamiast oryginalnego wyjątku SEH.Funkcja rethrow_exception generuje następnie przetłumaczony wyjątek, a nie wyjątek oryginalny.Aby uzyskać więcej informacji o funkcjach tłumacza SE, zobacz _set_se_translator.
Funkcja rethrow_exception
Po zapisaniu przechwyconego wyjątku w obiekcie exception_ptr, wątek główny może przetworzyć obiekt.W podstawowym wątku, wywołaj funkcję rethrow_exception wraz z obiektem exception_ptr jako argumentem.Funkcja rethrow_exception wyodrębnia wyjątek z obiektu exception_ptr i następnie zgłasza wyjątek w kontekście wątku głównego.Jeśli parametr p funkcji rethrow_exception ma wartość null exception_ptr, funkcja zgłasza std::bad_exception.
Wyodrębniony wyjątek jest teraz wyjątkiem bieżącym w wątku podstawowym i można go obsłużyć, podobnie jak w przypadku innych wyjątków.Jeśli przechwytujesz wyjątek, możesz go obsługiwać bezpośrednio lub używać instrukcji throw, aby wysłać go do kodu obsługi wyjątków wyższego poziomu.W przeciwnym razie nic nie rób, niech domyślny program obsługi wyjątków systemu zakończy proces.
Funkcja make_exception_ptr
Funkcja make_exception_ptr przyjmuje instancję klasy jako argument i zwraca exception_ptr , który odwołuje się do wystąpienia.Mimo że dowolny obiekt klasy może być argumentem, zwykle określaj obiekt klasa wyjątków jako argument funkcji make_exception_ptr.
Wywołanie funkcji make_exception_ptr jest odpowiednikiem zgłaszania wyjątku C++, przechwytywania go w bloku catch, a następnie wywoływania funkcji current_exception, która zwraca obiekt exception_ptr, który odwołuje się do tego wyjątku.Implementacja firmy Microsoft funkcji make_exception_ptr jest bardziej efektywna niż generowanie i następnie przechwytywanie wyjątku.
Aplikacja zazwyczaj nie wymaga funkcji make_exception_ptr, a my odradzamy jej użycie.
Przykład
Poniższy przykład transportuje standardowy wyjątek C++ i niestandardowy wyjątek C++ z jednego wątku do innego.
// 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;
}
Wymagania
Nagłówek: <wyjątek>
Zobacz też
Informacje
Obsługa wyjątków w języku Visual C++
Struktura EXCEPTION_RECORD
Składnia kodu obsługi
/clr (Kompilacja środowiska uruchomieniowego języka wspólnego)