Jegyzet
Az oldalhoz való hozzáférés engedélyezést igényel. Próbálhatod be jelentkezni vagy könyvtárat váltani.
Az oldalhoz való hozzáférés engedélyezést igényel. Megpróbálhatod a könyvtár váltását.
Az egyidejűségi futtatókörnyezet C++ kivételkezelést használ számos hiba közléséhez. Ezek a hibák közé tartozik a futtatókörnyezet érvénytelen használata, a futásidejű hibák, például az erőforrás beolvasásának meghiúsulása, valamint a tevékenységeknek és tevékenységcsoportoknak megadott munkahelyi függvényekben előforduló hibák. Amikor egy feladat vagy feladatcsoport kivételt dob, a futásidejű környezet megtartja a kivételt, és azt a környezetbe továbbítja, amely a feladat vagy feladatcsoport befejezésére vár. Az olyan összetevők esetében, mint az egyszerűsített feladatok és az ügynökök, a futtatókörnyezet nem kezeli a kivételeket. Ezekben az esetekben saját kivételkezelési mechanizmust kell implementálnia. Ez a témakör azt ismerteti, hogy a futtatókörnyezet hogyan kezeli a tevékenységek, feladatcsoportok, egyszerűsített feladatok és aszinkron ügynökök által kidobott kivételeket, és hogyan reagálhat az alkalmazások kivételeire.
Kulcsfontosságú pontok
Amikor egy feladat vagy feladatcsoport kivételt dob, a futásidejű környezet megtartja a kivételt, és azt a környezetbe továbbítja, amely a feladat vagy feladatcsoport befejezésére vár.
Ha lehetséges, vegye körül az egyidejűség minden hívását a concurrency::task::get és concurrency::task::wait függvényekhez egy
try/catchblokkal, hogy kezelhesse azokat a hibákat, amelyekből helyre tud állni. A futtatókörnyezet leállítja az alkalmazást, ha egy feladat kivételt dob, és ezt a kivételt a feladat, annak egyik folytatása vagy a fő alkalmazás nem kapja el.A tevékenységalapú folytatások mindig futnak; nem számít, hogy az előzményfeladat sikeresen befejeződött-e, kivételt jelzett vagy megszakított. Az értékalapú folytatás nem fut, ha az előzményfeladat elvet vagy megszakít.
Mivel a feladat-alapú folytatások automatikusan lefutnak, fontolja meg, hogy a folytatási lánc végén adjon-e hozzá egy ilyen folytatást. Ezzel garantálható, hogy a kód minden kivételt betart.
A futtatókörnyezet egyidejűség::task_canceled kivételt dob, amikor egyidejűség::task::get hívást végrehajt és az a feladat megszakításra kerül.
A futtatókörnyezet nem kezeli az egyszerűsített feladatok és ügynökök kivételeit.
Ebben a dokumentumban
Tevékenységek és folytatások
Ez a szakasz azt ismerteti, hogyan kezeli a futtatási környezet a concurrency::task objektumok és azok folytatásai által kiváltott kivételeket. A tevékenység- és folytatási modellről további információt a Tevékenység párhuzamossága című témakörben talál.
Amikor egy objektumnak task átadott munkafüggvény törzsében kivételt dob, a futtatókörnyezet tárolja azt, és átirányítja a kivételt annak a környezetnek, amely a concurrency::task::get vagy concurrency::task::wait függvényt hívja. A feladat-párhuzamosság dokumentum a tevékenységalapú és az értékalapú folytatásokat írja le, de összegzésként az értékalapú folytatás típusparamétert T , a tevékenységalapú folytatás pedig egy típusparamétert task<T>vesz igénybe. Ha egy dobó tevékenység egy vagy több értékalapú folytatással rendelkezik, a rendszer nem ütemezi a folytatások futtatását. Az alábbi példa a következő viselkedést szemlélteti:
// 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.
*/
A tevékenységalapú folytatással kezelheti az előzményfeladat által okozott kivételeket. A tevékenységalapú folytatások mindig futnak; nem számít, hogy a feladat sikeresen befejeződött, kivételt jelzett vagy megszakított. Amikor egy feladat kivételt dob, a feladatalapú folytatások ütemezésre kerülnek a futtatásra. Az alábbi példa egy olyan feladatot mutat be, amely mindig hibát okoz. A tevékenységnek két folytatása van; Az egyik értékalapú, a másik feladatalapú. A tevékenységalapú kivétel mindig fut, ezért képes elkapni az előzményfeladat által okozott kivételt. Amikor a példa mindkét folytatás befejezésére vár, a kivétel ismét kivétel lesz, mert a feladatkivétel mindig kiváltódik, amikor task::get vagy task::wait meghívásra kerül.
// 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.
*/
Javasoljuk, hogy a feladatalapú folytatásokkal észlelje azokat a kivételeket, amelyeket kezelni tud. Mivel a feladat-alapú folytatások automatikusan lefutnak, fontolja meg, hogy a folytatási lánc végén adjon-e hozzá egy ilyen folytatást. Ezzel garantálható, hogy a kód minden kivételt betart. Az alábbi példa egy alapvető értékalapú folytatási láncot mutat be. A lánc harmadik feladata hibaüzenetet ad, ezért az azt követő értékalapú kezelések nem futnak. A végső lépés azonban feladatalapú, ezért mindig végrehajtódik. Ez az utolsó folytatás kezeli a harmadik tevékenység által okozott kivételt.
Javasoljuk, hogy a lehető legspecifikusabb kivételeket kezelje. Kihagyhatja ezt az utolsó feladat-alapú folytatást, ha nincsenek konkrét hibák, amelyeket kezelni kell. Minden kivétel kezeletlen marad, és megszüntetheti az alkalmazást.
// 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>
*/
Jótanács
Az concurrency::task_completion_event::set_exception metódust használhatja arra, hogy kivételt társítson egy feladatbefejezési eseményhez. A Task Parallelism dokumentum részletesen bemutatja a concurrency::task_completion_event osztályt.
concurrency::task_canceled egy fontos futásidejű kivételtípus, amely a következőhöz kapcsolódik: task. A futtatókörnyezet a híváskor task_canceled indul task::get el, és a feladat megszakad. (Ezzel szemben task::wait a task_status::canceled értéket adja vissza és nem dob.) Ezt a kivételt el is kaphatja és kezelheti egy tevékenységalapú folytatásból vagy híváskor task::get. A feladattörléssel kapcsolatos további információkért lásd : Lemondás a PPL-ben.
Figyelmeztetés
Soha ne használja a "throw" kifejezést a kódban. Hívja inkább a concurrency::cancel_current_task függvényt.
A futtatókörnyezet leállítja az alkalmazást, ha egy feladat kivételt dob, és ezt a kivételt a feladat, annak egyik folytatása vagy a fő alkalmazás nem kapja el. Ha az alkalmazás összeomlik, beállíthatja a Visual Studiót, hogy megszakítsa a futást, amikor C++ kivételeket dobnak. Miután diagnosztizálta a kezeletlen kivétel helyét, a kezeléséhez használjon feladatalapú folytatást.
A jelen dokumentumban Futtatókörnyezet által kiváltott kivételek szakasz részletesebben ismerteti a futtatókörnyezeti kivételekkel való munkát.
[Felső]
Feladatcsoportok és párhuzamos algoritmusok
Ez a szakasz azt ismerteti, hogy a futtatókörnyezet hogyan kezeli a tevékenységcsoportok által kidobott kivételeket. Ez a szakasz olyan párhuzamos algoritmusokra is vonatkozik, mint az concurrency::parallel_for, mivel ezek az algoritmusok feladatcsoportokra épülnek.
Figyelmeztetés
Győződjön meg arról, hogy tisztában van a kivételek függő tevékenységekre gyakorolt hatásával. A kivételkezelés feladatokkal vagy párhuzamos algoritmusokkal való használatával kapcsolatos ajánlott eljárásokért tekintse meg a Párhuzamos minták tára témakör ajánlott eljárásainak a Törlés és kivételkezelés objektummegsemmisítésre gyakorolt hatásának ismertetése című szakaszát.
A tevékenységcsoportokkal kapcsolatos további információkért lásd: Feladat-párhuzamosság. A párhuzamos algoritmusokról további információt a Párhuzamos algoritmusok című témakörben talál.
Amikor egy munkafüggvény törzsében kivételt dob, amelyet concurrency::task_group vagy concurrency::structured_task_group objektumnak ad át, a futtatókörnyezet tárolja ezt a kivételt, és azt az összefüggéshez irányítja, amely a concurrency::task_group::wait, concurrency::structured_task_group::wait, concurrency::task_group::run_and_wait vagy concurrency::structured_task_group::run_and_wait függvényt hívja. A futtatókörnyezet emellett leállítja a tevékenységcsoportban lévő összes aktív tevékenységet (beleértve a gyermektevékenység-csoportokban lévőket is), és elveti a még el nem indított tevékenységeket.
Az alábbi példa egy kivételt okozó munkafüggvény alapstruktúráját mutatja be. A példa egy objektumot task_group használ két point objektum értékeinek párhuzamos nyomtatásához. A munkafüggvény print_point egy point objektum értékeit nyomtatja a konzolra. A munkafüggvény kivételt jelez, ha a bemeneti érték .NULL A futtatókörnyezet tárolja ezt a kivételt, és azt a task_group::wait-t hívó környezetbe juttatja.
// 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;
}
}
Ez a példa a következő kimenetet hozza létre.
X = 15, Y = 30Caught exception: point is NULL.
A feladatcsoportban kivételkezelést használó teljes példáért lásd : A kivételkezelés használata párhuzamos hurokból való megszakításhoz.
[Felső]
A futtatókörnyezet által kidobott kivételek
Kivételt okozhat a futtatókörnyezet hívása. A legtöbb kivételtípus programozási hibára utal, kivéve a concurrency::task_canceled és concurrency::operation_timed_out kivételeket. Ezek a hibák általában helyreállíthatatlanok, ezért nem szabad elkapni vagy kezelni az alkalmazáskóddal. Azt javasoljuk, hogy csak akkor kapja meg vagy kezelje a helyreállíthatatlan hibákat az alkalmazáskódban, ha programozási hibákat kell diagnosztizálnia. A futtatókörnyezet által definiált kivételtípusok megértése azonban segíthet a programozási hibák diagnosztizálásában.
A kivételkezelési mechanizmus ugyanaz, mint a futtatókörnyezet által kidobott kivételek esetében, mint a munkahelyi függvények által kidobott kivételek. Az concurrency::receive függvény például akkor dob operation_timed_out, ha nem kap üzenetet a megadott időszakban. Ha a receive egy feladatcsoportnak átadott munkafüggvényben kivételt jelez, a futtatókörnyezet tárolja a kivételt, és továbbítja azt a task_group::wait, structured_task_group::wait, task_group::run_and_wait, vagy structured_task_group::run_and_wait hívó környezetébe.
Az alábbi példa a concurrency::parallel_invoke algoritmust használja, hogy két feladatot párhuzamosan futtasson. Az első feladat öt másodpercet vár, majd üzenetet küld egy üzenetpuffernek. A második feladat a függvényt használja, receive hogy három másodpercet várjon egy üzenet fogadásához ugyanabból az üzenetpufferből. A receive függvény akkor dob, operation_timed_out ha az adott időszakban nem kapja meg az üzenetet.
// 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;
}
}
Ez a példa a következő kimenetet hozza létre.
The operation timed out.
Az alkalmazás rendellenes leállásának megakadályozása érdekében győződjön meg arról, hogy a kód kezeli a kivételeket a futtatókörnyezetbe való hívások során. A kivételeket szintén kezelje, amikor olyan külső kódot hív meg, amely a Konkurenciakezelő futtatókörnyezetet használja, például egy harmadik féltől származó könyvtárat.
[Felső]
Több kivétel
Ha egy feladat vagy párhuzamos algoritmus több kivételt is kap, a futtatókörnyezet csak az egyik kivételt alkalmazza a hívási környezetből. A futtatókörnyezet nem garantálja, hogy melyik kivételt kezeli.
Az alábbi példa a parallel_for algoritmus használatát mutatja be, amellyel számokat nyomtat a konzolra. Kivételt jelent, ha a bemeneti érték kisebb, mint egy minimális érték, vagy nagyobb, mint egy maximális érték. Ebben a példában több feladatfüggvény is okozhat kivételt.
// 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;
}
}
Az alábbiakban a példához tartozó mintakimenet látható.
8293104567Caught exception: -5: the value is less than the minimum.
[Felső]
Lemondás
Nem minden kivétel jelez hibát. Egy keresési algoritmus például kivételkezeléssel állíthatja le a társított feladatot, amikor megtalálja az eredményt. További információ a lemondási mechanizmusok kódban való használatáról: Lemondás a PPL-ben.
[Felső]
Egyszerűsített feladatok
A könnyűsúlyú feladat olyan feladat, amelyet közvetlenül egy egyidejűség::Scheduler objektumból ütemez. Az egyszerűsített feladatok kevesebb többletterhelést hordoznak magukban, mint a szokásos feladatok. A futtatókörnyezet azonban nem észlel kivételeket, amelyeket az egyszerűsített feladatok okoznak. Ehelyett a kivételt a kezeletlen kivételkezelő fogja elkapni, amely alapértelmezés szerint leállítja a folyamatot. Ezért használjon egy megfelelő hibakezelési mechanizmust az alkalmazásban. Az egyszerűsített feladatokról további információt a Feladatütemezőben talál.
[Felső]
Aszinkron ügynökök
Az egyszerűsített feladatokhoz hasonlóan a futtatókörnyezet sem kezeli az aszinkron ügynökök által kidobott kivételeket.
Az alábbi példa egy módszert mutat be egy concurrency::agent osztályból származó osztály kivételeinek kezelésére. Ez a példa az osztályt points_agent határozza meg. A points_agent::run metódus beolvassa point az objektumokat az üzenetpufferből, és kinyomtatja őket a konzolra. A run metódus kivételt jelez, ha mutatót NULL kap.
A run metódus egy blokkban lévő try-catch összes munkát körülveszi. A catch blokk egy üzenetpufferben tárolja a kivételt. Az alkalmazás ellenőrzi, hogy az ügynök hibát észlelt-e, miután befejezte a feladatát, azáltal, hogy elolvassa a puffer tartalmát.
// 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;
}
Ez a példa a következő kimenetet hozza létre.
X: 10 Y: 20
X: 20 Y: 30
error occurred in agent: point must not be NULL
the status of the agent is: done
Mivel a blokk a try-catchwhile cikluson kívül létezik, az ügynök az első hiba észlelésekor befejezi a feldolgozást. Ha a try-catch blokk a while cikluson belül volna, az ügynök egy hiba bekövetkezése után is folytatná a működését.
Ez a példa egy üzenetpufferben tárolja a kivételeket, így egy másik összetevő figyelheti az ügynököt a futtatás során felmerülő hibákért. Ez a példa concurrency::single_assignment objektumot használ a hiba tárolásához. Abban az esetben, ha egy ügynök több kivételt kezel, az single_assignment osztály csak az első, neki átadott üzenetet tárolja. Ha csak az utolsó kivételt szeretné tárolni, használja a Concurrency::overwrite_buffer osztályt. Az összes kivétel tárolásához használja a concurrency::unbounded_buffer osztályt. Az üzenetblokkokról további információt az Aszinkron üzenetblokkok című témakörben talál.
Az aszinkron ügynökökről további információt az Aszinkron ügynökök című témakörben talál.
[Felső]
Összefoglalás
[Felső]
Lásd még
Párhuzamossági futtatókörnyezet
Feladat-párhuzamosság
Párhuzamos algoritmusok
Lemondás a PPL-ben
Feladatütemező
Aszinkron ügynökök