並行執行階段的例外狀況處理
並行執行時間會使用 C++ 例外狀況處理來傳達許多種類的錯誤。 這些錯誤包括不正確執行時間使用、執行階段錯誤,例如無法取得資源,以及您提供給工作和工作組的工作函式中發生的錯誤。 當工作或工作組擲回例外狀況時,執行時間會保留該例外狀況,並將它封送處理至等候工作或工作組完成的內容。 針對輕量型工作和代理程式等元件,執行時間不會為您管理例外狀況。 在這些情況下,您必須實作自己的例外狀況處理機制。 本主題描述執行時間如何處理工作、工作組、輕量型工作和非同步代理程式擲回的例外狀況,以及如何回應應用程式中的例外狀況。
重點
當工作或工作組擲回例外狀況時,執行時間會保留該例外狀況,並將它封送處理至等候工作或工作組完成的內容。
可能的話,請以區塊括住對並行::task::get 和 concurrency::task::wait /
try
catch
的每個呼叫 ,以處理您可以從中復原 的錯誤。 如果工作擲回例外狀況,且工作未攔截該例外狀況、其中一個接續或主要應用程式,則執行時間會終止應用程式。工作型接續一律會執行;不論前項工作是否已順利完成、擲回例外狀況或已取消,都無關緊要。 如果前項工作擲回或取消,則以值為基礎的接續不會執行。
由於工作型接續一律執行,請考慮是否要在接續鏈結結尾新增以工作為基礎的接續。 這有助於確保您的程式碼會觀察所有例外狀況。
當您呼叫 concurrency::task::get 並取消該工作時,執行時間會 擲回並行::task_canceled 。
執行時間不會管理輕量型工作和代理程式的例外狀況。
本文內容
工作和接續
本節說明執行時間如何處理並行::task 物件及其接續所擲回 的例外狀況。 如需工作和接續模型的詳細資訊,請參閱 工作平行處理原則 。
當您在傳遞至 task
物件的工作函式主體中擲回例外狀況時,執行時間會儲存該例外狀況,並將它封送處理至呼叫 concurrency::task::get 或 concurrency::task::wait 的內容。 檔 「工作平行處理原則 」描述以工作為基礎的與以值為基礎的接續,但為了摘要說明,以值為基礎的接續會採用 型 T
別的參數,而以工作為基礎的接續則採用 型 task<T>
別的參數。 如果擲回的工作有一或多個以值為基礎的接續,則不會排程執行這些接續。 下列範例可說明此行為:
// 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.
*/
工作型接續可讓您處理前項工作擲回的任何例外狀況。 工作型接續一律會執行;不論工作是否已順利完成、擲回例外狀況或已取消,都無關緊要。 當工作擲回例外狀況時,其工作型接續會排程執行。 下列範例顯示一律擲回的工作。 工作有兩個接續:一個是以值為基礎,另一個是以工作為基礎。 工作型例外狀況一律會執行,因此可以攔截前項工作擲回的例外狀況。 當範例等候這兩個接續完成時,會再次擲回例外狀況,因為呼叫 或 task::wait
時 task::get
一律會擲回工作例外狀況。
// 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.
*/
我們建議您使用工作型接續來攔截能夠處理的例外狀況。 由於工作型接續一律執行,請考慮是否要在接續鏈結結尾新增以工作為基礎的接續。 這有助於確保您的程式碼會觀察所有例外狀況。 下列範例顯示基本值型接續鏈結。 鏈結中的第三個工作會擲回,因此不會執行後續的任何以值為基礎的接續。 不過,最終接續是以工作為基礎,因此一律會執行。 這個最後的接續會處理第三個工作擲回的例外狀況。
建議您擷取您可以捕捉到的最特定例外狀況。 如果您沒有要攔截的特定例外狀況,可以省略此以工作為基礎的最終接續。 任何例外狀況都會維持未處理狀態,而且可以終止應用程式。
// 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>
*/
提示
您可以使用 concurrency::task_completion_event::set_exception 方法來將例外狀況與工作完成事件產生關聯。 檔 工作平行處理原則 會更詳細地 描述並行::task_completion_event 類別。
concurrency::task_canceled 是與 相關的 task
重要執行時間例外狀況類型。 當您呼叫 task::get
並取消該工作時,執行時間會 task_canceled
擲回。 (相反地, task::wait
傳 回task_status::canceled 且不會擲回。您可以從工作型接續或呼叫 task::get
時攔截並處理此例外狀況。 如需工作取消的詳細資訊,請參閱 PPL 中的取消。
警告
永遠不會從您的程式碼擲回 task_canceled
。 請改為呼叫 並行::cancel_current_task 。
如果工作擲回例外狀況,且工作未攔截該例外狀況、其中一個接續或主要應用程式,則執行時間會終止應用程式。 如果您的應用程式當機,您可以設定 Visual Studio 在擲回 C++ 例外狀況時中斷。 診斷未處理的例外狀況位置之後,請使用以工作為基礎的接續來處理它。
本檔中執行時間 擲回的例外狀況一節 說明如何更詳細地處理執行時間例外狀況。
[靠上]
工作組和平行演算法
本節描述執行時間如何處理工作組擲回的例外狀況。 本節也適用于平行算法,例如 concurrency::p arallel_for ,因為這些演算法建置在工作組上。
警告
請確定您瞭解例外狀況對相依工作的影響。 如需如何搭配工作或平行演算法使用例外狀況處理的建議做法,請參閱 平行模式程式庫中最佳做法主題中的<瞭解取消和例外狀況處理如何影響物件解構 >一節。
如需工作組的詳細資訊,請參閱 工作平行處理原則 。 如需平行演算法的詳細資訊,請參閱 平行演算法 。
當您在傳遞至 並行::task_group或 平行存取::structured_task_group 物件的工作函式主體中擲回例外狀況時,執行時間會儲存該例外狀況,並將它封送處理至呼叫 concurrency::task_group ::wait、 concurrency::structured_task_group::wait 、 concurrency::task_group::run_and_wait 或 平行存取的內容:structured_task_group::run_and_wait 。 執行時間也會停止工作組中的所有使用中工作(包括子工作組中的工作),並捨棄尚未啟動的任何工作。
下列範例顯示擲回例外狀況之工作函式的基本結構。 此範例會使用 物件來平行列印兩 point
個 task_group
物件的值。 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 = 30Caught exception: point is NULL.
如需在工作組中使用例外狀況處理的完整範例,請參閱 如何:使用例外狀況處理從平行迴圈 中斷。
[靠上]
執行時間擲回的例外狀況
例外狀況可能是由呼叫執行時間所造成。 除了並行::task_canceled和 並行::operation_timed_out 以外,大部分的 例外狀況類型都表示程式設計錯誤。 這些錯誤通常無法復原,因此不應該由應用程式程式碼攔截或處理。 當您需要診斷程式設計錯誤時,建議您只在應用程式程式碼中攔截或處理無法復原的錯誤。 不過,瞭解執行時間所定義的例外狀況類型可協助您診斷程式設計錯誤。
例外狀況處理機制與工作函式擲回的例外狀況相同,適用于執行時間擲回的例外狀況。 例如,當 並行::receive 函式未在指定的時段內收到訊息時,就會擲回 operation_timed_out
。 如果在 receive
您傳遞給工作組的工作函式中擲回例外狀況,執行時間會儲存該例外狀況,並將它封送處理至呼叫 task_group::wait
、 structured_task_group::wait
task_group::run_and_wait
或 structured_task_group::run_and_wait
的內容。
下列範例會使用 並行::p arallel_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;
}
}
以下顯示此範例的範例輸出。
8293104567Caught exception: -5: the value is less than the minimum.
[靠上]
取消
並非所有例外狀況都表示錯誤。 例如,搜尋演算法可能會在找到結果時,使用例外狀況處理來停止其相關聯的工作。 如需如何在程式碼中使用取消機制的詳細資訊,請參閱 PPL 中的取消。
[靠上]
輕量型工作
輕量型工作是您直接從 並行::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 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;
}
此範例會產生下列輸出。
X: 10 Y: 20
X: 20 Y: 30
error occurred in agent: point must not be NULL
the status of the agent is: done
try
catch
- 因為區塊存在於迴圈之外 while
,所以代理程式會在遇到第一個錯誤時結束處理。 try
catch
- 如果區塊位於迴圈內 while
,代理程式會在發生錯誤之後繼續。
此範例會將例外狀況儲存在訊息緩衝區中,讓另一個元件可以在執行時監視代理程式是否有錯誤。 此範例會使用 並行::single_assignment 物件來儲存錯誤。 在代理程式處理多個例外狀況的情況下,類別 single_assignment
只會儲存傳遞給它的第一個訊息。 若要只儲存最後一個例外狀況,請使用 concurrency::overwrite_buffer 類別。 若要儲存所有例外狀況,請使用 concurrency::unbounded_buffer 類別。 如需這些訊息區塊的詳細資訊,請參閱 非同步消息塊 。
如需非同步代理程式的詳細資訊,請參閱 非同步代理程式 。
[靠上]
摘要
[靠上]