共用方式為


並行執行階段的例外狀況處理

並行執行階段會使用 C++ 例外狀況處理來溝通許多類型的錯誤。 這些錯誤包括在工作 (Work) 函式中發生的執行階段、失敗之類的執行階段錯誤保護資源和錯誤的不正確地使用您提供給工作和工作群組。 當工作或工作群組會擲回例外狀況時,執行階段會保留例外狀況並將它封送處理至等候工作或工作群組完成的內容。 執行階段並不會管理輕量型工作和代理程式這類元件的例外狀況。 在這些情況下,您必須實作自己的例外狀況處理機制。 本主題說明執行階段如何處理工作、工作群組、輕量型工作和非同步代理程式所擲回的例外狀況和如何回應應用程式中的例外狀況。

金鑰

  • 當工作或工作群組會擲回例外狀況時,執行階段會保留例外狀況並將它封送處理至等候工作或工作群組完成的內容。

  • 如果可能,請括住每個呼叫 concurrency::task::getconcurrency::task::wait 以 try/catch 區塊處理可復原的錯誤。 執行階段結束應用程式,如果工作擲回例外狀況,該例外狀況是由工作攔截,其接續是或主應用程式。

  • 以工作為基礎的接續一定會執行;不重要前項工作成功完成,是否擲回例外狀況,也不會被移除。 如果前項工作擲回或已取消,數值的接續工作無法執行。

  • 因為工作式一定會繼續執行,請考量是否要加入工作說明繼續在接續鏈結的尾端。 這可確保程式碼檢視所有的例外狀況。

  • 執行階段會擲回 concurrency::task_canceled ,當您呼叫 concurrency::task::get 時,且該工作已取消。

  • 執行階段不會處理輕量型工作和代理程式的例外狀況。

在文件中。

  • 工作和繼續

  • 工作群組和平行演算法

  • 執行階段所擲回的例外狀況

  • 多個例外狀況

  • 取消

  • 輕量型工作

  • 非同步代理程式

工作和繼續

本節說明執行階段如何處理 concurrency::task 物件及其接續所擲回的例外狀況。 如需工作並繼續模型的詳細資訊,請參閱 工作平行處理原則 (並行執行階段)

當您擲回在傳遞至 task 物件的工作函式主體中擲回例外狀況時,執行階段會儲存該例外狀況並將它封送處理至呼叫 concurrency::task::getconcurrency::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::gettask::wait 時呼叫。

// 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 value-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 方法會將例外狀況與工作完成事件。文件 工作平行處理原則 (並行執行階段) 詳細說明 concurrency::task_completion_event 類別。

concurrency::task_canceledtask相關的一個重要的執行階段例外狀況型別。 執行階段會擲回 task_canceled ,當您呼叫 task::get 時,且該工作已取消。 (相反地, task::wait 傳回 task_status::canceled ,而且不會擲回)。您可以攔截和處理工作說明繼續進行這個例外狀況,或當您呼叫 task::get。 如需取消工作的詳細資訊,請參閱 PPL 中的取消

警告

不要擲回的程式碼中 task_canceled 。呼叫 concurrency::cancel_current_task

執行階段結束應用程式,如果工作擲回例外狀況,該例外狀況是由工作攔截,其接續是或主應用程式。 如果應用程式損毀,您可以設定 Visual Studio 中斷,當 C++ 例外狀況時。 在您診斷未處理之例外狀況的位置之後,請使用工作說明繼續處理它。

在文件中的章節。 執行階段所擲回的例外狀況。 描述如何將更詳細地與執行階段例外狀況時使用。

[]上述

工作群組和平行演算法

本節說明執行階段會如何處理工作群組所擲回的例外狀況。 本節也適用於之類的平行演算法 (例如, concurrency::parallel_for,因為在工作群組中的這些演算法是建立。

警告

請確定您了解例外狀況對相依工作造成的影響。如需如何使用例外狀況處理搭配工作或平行演算法的建議作法,請參閱<平行模式程式庫中的最佳作法>主題的Understand how Cancellation and Exception Handling Affect Object Destruction一節。

如需工作群組的詳細資訊,請參閱工作平行處理原則 (並行執行階段)。 如需平行演算法的詳細資訊,請參閱平行演算法

當您擲回在傳遞至 concurrency::task_groupconcurrency::structured_task_group 物件的工作函式主體中擲回例外狀況時,執行階段會儲存該例外狀況並將它封送處理至呼叫、、 concurrency::task_group::waitconcurrency::structured_task_group::waitconcurrency::task_group::run_and_waitconcurrency::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::task_canceledconcurrency::operation_timed_out,表示發生程式設計錯誤。 這些錯誤通常無法復原,因此不應該讓應用程式碼攔截或處理它們。 建議您只有在需要診斷程式設計錯誤時,才在應用程式程式碼中攔截或處理無法復原的錯誤。 不過,了解執行階段所定義的例外狀況型別,可協助您診斷程式設計錯誤。

執行階段所擲回的例外狀況,與工作函式所擲回的例外狀況,兩者使用的例外處理機制相同。 例如,在中,在指定的時間,內收到訊息 concurrency::receive 函式擲回 operation_timed_out 。 如果 receive 在您傳遞給工作 (Task) 群組的工作 (Work) 函式中擲回例外狀況,則執行階段會儲存該例外狀況,並將它封送處理至呼叫 task_group::waitstructured_task_group::waittask_group::run_and_waitstructured_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 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 迴圈內部,則代理程式會在發生錯誤之後繼續進行。

這個範例會將例外狀況儲存在訊息緩衝區中,讓另一個元件可以在代理程式執行時監視代理程式遇到的錯誤。 這個範例會使用 concurrency::single_assignment 物件來儲存錯誤。 在代理程式處理多個例外狀況的情況下,single_assignment 類別只會儲存傳遞給它的第一個訊息。 若只要儲存最後一個例外狀況,請使用 concurrency::overwrite_buffer 類別。 若要儲存所有例外狀況,請使用 concurrency::unbounded_buffer 類別。 如需這些訊息區塊的詳細資訊,請參閱非同步訊息區

如需非同步代理程式的詳細資訊,請參閱非同步代理程式

[]上述

摘要

[]上述

請參閱

概念

並行執行階段

工作平行處理原則 (並行執行階段)

平行演算法

PPL 中的取消

工作排程器 (並行執行階段)

非同步代理程式