並行執行階段的例外處理
更新:2011 年 3 月
並行執行階段會使用 C++ 例外處理來溝通許多類型的錯誤。 這些錯誤包括執行階段的無效用法、如取得資源失敗之類的執行階段錯誤,以及在您提供給工作 (Task) 群組的工作 (Work) 函式中發生的錯誤。 本主題說明執行階段會如何處理由工作群組、輕量型工作和非同步代理程式所擲回的例外狀況,以及如何在您的應用程式中回應例外狀況。
章節
工作群組和平行演算法
輕量型工作
非同步代理程式
常見的例外狀況
摘要
工作群組和平行演算法
本節說明執行階段會如何處理工作群組所擲回的例外狀況。 本節也適用於 Concurrency::parallel_for 之類的平行演算法,因為這些演算法是建立在工作群組上。
警告
請確定您了解例外狀況對相依工作造成的影響。 如需如何使用例外處理搭配工作或平行演算法的建議作法,請參閱<平行模式程式庫中的最佳作法>主題的了解取消和例外處理對物件解構的影響一節。
如需工作群組的詳細資訊,請參閱工作平行處理原則 (並行執行階段)。 如需平行演算法的詳細資訊,請參閱平行演算法。
工作函式所擲回的例外狀況
「工作函式」(Work Function) 是傳遞給執行階段的 Lambda 函式、函式物件或函式指標。 當您將工作函式傳遞給工作群組時,執行階段會不同的內容個別執行該工作函式。
當您在傳遞給 Concurrency::task_group 或 Concurrency::structured_task_group 物件的工作函式的主體中擲回例外狀況時,執行階段會儲存該例外狀況,並將它封送處理至呼叫 Concurrency::task_group::wait、Concurrency::structured_task_group::wait、Concurrency::task_group::run_and_wait 或 Concurrency::structured_task_group::run_and_wait 的內容。 執行階段也會停止工作群組中的所有作用中工作 (包括子工作群組中的工作),並捨棄任何尚未啟動的工作。
下列範例顯示會擲回例外狀況之工作函式的基本結構。 此範例會使用 task_group 物件,以平行方式列印兩個 point 物件的值。 print_point 工作函式會將 point 物件的值列印至主控台。 如果輸入值是 NULL,則工作函式會擲回例外狀況。 執行階段會儲存這個例外狀況,並將它封送處理至呼叫 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;
}
}
這個範例會產生下列輸出。
X = 15, Y = 30
Caught exception: point is NULL.
如需在工作群組中使用例外處理的完整範例,請參閱 HOW TO:使用例外狀況處理來中斷平行迴圈。
執行階段所擲回的例外狀況
除了工作函式之外,呼叫執行階段也可能是引發例外狀況的原因。 執行階段所擲回的大部分例外狀況都是表示發生程式設計錯誤。 這些錯誤通常無法復原,因此不應該讓應用程式碼攔截或處理它們。 不過,了解執行階段所定義的例外狀況型別,可協助您診斷程式設計錯誤。 常見的例外狀況一節說明常見的例外狀況以及這些例外狀況發生的條件。
執行階段所擲回的例外狀況,與工作函式所擲回的例外狀況,兩者使用的例外處理機制相同。 例如,Concurrency::receive 函式會在未於指定的時間週期內收到訊息時,擲回 operation_timed_out。 如果 receive 在您傳遞給工作 (Task) 群組的工作 (Work) 函式中擲回例外狀況,則執行階段會儲存該例外狀況,並將它封送處理至呼叫 task_group::wait、structured_task_group::wait、task_group::run_and_wait 或 structured_task_group::run_and_wait 的內容。
下列範例會使用 Concurrency::parallel_invoke 演算法,以平行方式執行兩項工作。 第一項工作會先等候五秒,再將訊息傳送至訊息緩衝區。 第二項工作會使用 receive 函式,花三秒鐘的時間等候同一個訊息緩衝區送來訊息。 如果 receive 函式未在這個時間週期內收到訊息,則會擲回 operation_timed_out。
// 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;
}
}
這個範例會產生下列輸出。
The operation timed out.
為了防止應用程式異常結束,請確定您的程式碼能夠處理執行階段發生的例外狀況。 另外也請處理使用並行執行階段的外部程式碼 (例外協力廠商程式庫) 所引發的例外狀況。
多個例外狀況
如果工作或平行演算法收到多個例外狀況,則執行階段只會將其中一個例外狀況封送處理至呼叫端內容。 執行階段無法指出它一定會封送處理哪個例外狀況。
下列範例會使用 parallel_for 演算法,將數字列印至主控台。 如果輸入值小於某個最小值或是大於某個最大值,則會擲回例外狀況。 在這個範例中,可能有多個工作函式會擲回例外狀況。
// 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;
}
}
下列顯示這個範例 (Example) 的範例 (Sample) 輸出。
8
2
9
3
10
4
5
6
7
Caught exception: -5: the value is less than the minimum.
取消
並非所有的例外狀況都表示發生錯誤。 例如,搜尋演算法可能會在找到結果時,使用例外處理來停止其相關聯的工作。 如需如何在程式碼中使用取消機制的詳細資訊,請參閱PPL 中的取消。
回到頁首
輕量型工作
輕量型工作是指您直接從 Concurrency::Scheduler 物件排定的工作。 輕量型工作帶來的負荷少於一般工作。 不過,執行階段並不會攔截輕量型工作所擲回的例外狀況。 這些例外狀況是由未處理例外處理常式來攔截,而這個處理常式預設會結束處理序。 因此,請在應用程式中使用適當的錯誤處理機制。 如需輕量型工作的詳細資訊,請參閱工作排程器 (並行執行階段)。
回到頁首
非同步代理程式
與對待輕量型工作的方式類似,執行階段並不會管理非同步代理程式所擲回的例外狀況。
下列範例顯示一個在衍生自 Concurrency::agent 的類別中處理例外狀況的方式。 這個範例會定義 points_agent 類別。 points_agent::run 方法會讀取訊息緩衝區中的 point 物件,並將這些物件列印至主控台。 run 方法會在收到 NULL 指標時擲回例外狀況。
run 方法會將所有工作都包在 try-catch 區塊中。 catch 區塊會將例外狀況儲存在訊息緩衝區中。 應用程式會在代理程式完成之後讀取這個緩衝區,以檢查代理程式是否發生錯誤。
// 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 occured 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 occured 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;
}
這個範例會產生下列輸出。
X: 10 Y: 20
X: 20 Y: 30
error occured in agent: point must not be NULL
the status of the agent is: done
因為 try-catch 區塊存在於 while 迴圈外部,所以代理程式會在發生第一個錯誤時結束處理。 如果 try-catch 區塊是在 while 迴圈內部,則代理程式會在發生錯誤之後繼續進行。
這個範例會將例外狀況儲存在訊息緩衝區中,讓另一個元件可以在代理程式執行時監視代理程式遇到的錯誤。 這個範例會使用 Concurrency::single_assignment 物件來儲存錯誤。 在代理程式處理多個例外狀況的情況下,single_assignment 類別只會儲存傳遞給它的第一個訊息。 若只要儲存最後一個例外狀況,請使用 Concurrency::overwrite_buffer 類別。 若要儲存所有例外狀況,請使用 Concurrency::unbounded_buffer 類別。 如需這些訊息區塊的詳細資訊,請參閱非同步訊息區。
如需非同步代理程式的詳細資訊,請參閱非同步代理程式。
回到頁首
常見的例外狀況
下表列出在並行執行階段中常見的例外狀況類別,並提供擲回例外狀況的條件。 除了 operation_timed_out 和 unsupported_os 之外,大部分的例外狀況類型都表示發生程式設計錯誤。 這些錯誤通常無法復原,因此不應該讓應用程式碼攔截或處理它們。 建議您只有在需要診斷程式設計錯誤時,才在應用程式程式碼中攔截或處理無法復原的錯誤。
例外狀況類別 |
條件 |
---|---|
您傳遞無效的訊息區塊指標。 |
|
內容嘗試將它自己解除封鎖。 |
|
執行階段嘗試將某個內容解除封鎖,但該內容已經解除封鎖。 |
|
嘗試將排程器設定為預設排程器,但是已有預設排程器。 |
|
取得不當的鎖定。 |
|
某個內容連結至相同的排程器多次。 |
|
執行階段所內部管理的內容已中斷與其排程器的連結,或該內容未與任何排程器連結。 |
|
內容在正在關閉的排程器上遞增參考計數器,而且該內容不在排程器內部。 |
|
相同的物件多次連結至某個訊息區塊。 |
|
多次排定某個尚未完成的工作。 |
|
執行階段執行無效的作業。 |
|
停用根本未啟用的過度訂閱。 |
|
提供無效的原則機碼給 Concurrency::SchedulerPolicy 物件。 |
|
指定給 SchedulerPolicy 物件的最大並行層級小於最小並行層級。 |
|
提供無效的原則值給 SchedulerPolicy 物件。 |
|
訊息區塊找不到所要求的訊息。 |
|
在呼叫 Concurrency::task_group::wait 方法或 Concurrency::structured_task_group::wait 方法之前,就已終結工作群組物件。 |
|
巢狀排程器無法正確中斷自己與父代的連結。 |
|
作業未在指定的時間週期內完成。 |
|
內容嘗試中斷與其排程器的連結,但是該內容未與任何排程器連結。 |
|
執行階段無法取得關鍵資源,例如作業系統所提供的資源。 |
|
目前的作業系統不支援執行階段。 |
回到頁首
摘要
當工作擲回例外狀況時,執行階段會保留該例外狀況,並將其封送處理至等候這個工作群組完成的內容。 執行階段並不會管理輕量型工作和代理程式這類元件的例外狀況。 在這些情況下,您必須實作自己的例外處理機制。
回到頁首
請參閱
概念
變更記錄
日期 |
記錄 |
原因 |
---|---|---|
2011 年 3 月 |
加入例外狀況對相依工作可能造成影響的警告性注意事項。 |
資訊加強。 |