共用方式為


PPL 中的取消

本文件說明如何取消平行模式程式庫 (PPL) 中的角色、如何取消平行工作,以及如何判斷平行工作是否已取消。

注意事項注意事項

執行階段使用例外狀況處理來實作取消作業。請不要攔截或處理程式碼中的這些例外狀況。此外,建議您函式主體中為您的工作撰寫無例外狀況之虞的程式碼。例如,當工作的主體中擲回例外狀況時,您可以使用「資源擷取為初始設定」(Resource Acquisition Is Initialization,RAII) 模式確保正確處理資源。如需使用 RAII 模式在可取消的工作中清除資源的完整範例,請參閱逐步解說:從使用者介面執行緒中移除工作

重點

本文內容

  • 平行工作樹狀結構

  • 取消平行工作

    • 使用取消語彙基元來取消平行工作

    • 使用取消方法來取消平行工作

    • 使用例外狀況取消平行工作

  • 取消平行演算法

  • 不使用取消的時機

平行工作樹狀結構

PPL 可使用工作和工作群組來管理更精細的工作和計算。 您可以將工作群組巢狀化,以形成平行工作的「樹狀結構」(Tree)。 下圖顯示平行工作樹狀結構。 在圖中, tg1tg2 表示工作群組; t1t2t3t4t5 表示工作群組所執行的工作。

平行工作樹狀結構

下列範例顯示在圖例中建立樹狀結構時所需的程式碼。 在此範例中,tg1tg2concurrency::structured_task_group 物件;t1t2t3t4t5concurrency::task_handle 物件。

// task-tree.cpp 
// compile with: /c /EHsc
#include <ppl.h>
#include <sstream>
#include <iostream>
#include <sstream>

using namespace concurrency;
using namespace std;

void create_task_tree()
{   
   // Create a task group that serves as the root of the tree.
   structured_task_group tg1;

   // Create a task that contains a nested task group.
   auto t1 = make_task([&] {
      structured_task_group tg2;

      // Create a child task.
      auto t4 = make_task([&] {
         // TODO: Perform work here.
      });

      // Create a child task.
      auto t5 = make_task([&] {
         // TODO: Perform work here.
      });

      // Run the child tasks and wait for them to finish.
      tg2.run(t4);
      tg2.run(t5);
      tg2.wait();
   });

   // Create a child task.
   auto t2 = make_task([&] {
      // TODO: Perform work here.
   });

   // Create a child task.
   auto t3 = make_task([&] {
      // TODO: Perform work here.
   });

   // Run the child tasks and wait for them to finish.
   tg1.run(t1);
   tg1.run(t2);
   tg1.run(t3);
   tg1.wait();   
}

您也可以使用 concurrency::task_group 類別建立類似的工作樹狀結構。 concurrency::task 類別也支援工作樹狀結構的概念。 不過, task 樹狀結構是相依樹狀結構。 在 task 樹狀結構,未來的工作在目前工作完成。 在工作群組樹狀目錄,內部工作在外部工作之前完成。 如需工作和工作群組之間差異的詳細資訊,請參閱工作平行處理原則 (並行執行階段)

[上方]

取消平行工作

有多種方式可以取消平行工作。 比較好的方法是使用取消語彙基元。 工作群組也支援 concurrency::task_group::cancel 方法和 concurrency::structured_task_group::cancel 方法會將工作群組設為已取消的狀態。 最後一種方式是在工作的工作函式主體中擲回例外狀況。 不論您選擇何種方法,取消並不會立即執行。 雖然新的工作尚未啟動,如果工作或工作群組取消,作用中工作必須檢查並回應取消。

如需取消平行工作的相關範例,請參閱逐步解說:使用工作和 XML HTTP 要求連線, 如何:使用取消來中斷平行迴圈如何:使用例外狀況處理來中斷平行迴圈

使用取消語彙基元來取消平行工作

task, task_groupstructured_task_group 類別可使用取消語彙基元支援取消作業。 PPL 提供此定義 concurrency::cancellation_token_sourceconcurrency::cancellation_token 類別。 當您使用取消語彙基元取消工作時,執行階段不會啟動訂閱這個語彙基元的新工作。 工作已在使用中監視自己的取消語彙基元和在他可以停止時停止。

若要啟始取消,請呼叫 concurrency::cancellation_token_source::cancel 方法。 您回應取消使用這些方法:

下列範例顯示工作取消的第一個基本模式。 工作主體偶爾檢查迴圈中的取消。

// task-basic-cancellation.cpp 
// compile with: /EHsc
#include <ppltasks.h>
#include <iostream>
#include <sstream>

using namespace concurrency;
using namespace std;

bool do_work()
{
    // Simulate work.
    wcout << L"Performing work..." << endl;
    wait(250);
    return true;
}

int wmain()
{
    cancellation_token_source cts;
    auto token = cts.get_token();

    wcout << L"Creating task..." << endl;

    // Create a task that performs work until it is canceled.
    auto t = create_task([]
    {
        bool moreToDo = true;
        while (moreToDo)
        {
            // Check for cancellation. 
            if (is_task_cancellation_requested())
            {
                // TODO: Perform any necessary cleanup here... 

                // Cancel the current task.
                cancel_current_task();
            }
            else 
            {
                // Perform work.
                moreToDo = do_work();
            }
        }
    }, token);

    // Wait for one second and then cancel the task.
    wait(1000);

    wcout << L"Canceling task..." << endl;
    cts.cancel();

    // Wait for the task to cancel.
    wcout << L"Waiting for task to complete..." << endl;
    t.wait();

    wcout << L"Done." << endl;
}

/* Sample output:
    Creating task...
    Performing work...
    Performing work...
    Performing work...
    Performing work...
    Canceling task...
    Waiting for task to complete...
    Done.
*/

cancel_current_task 函式會擲回;因此,您不需要從目前迴圈或函式明確地傳回。

提示

或者,您可以呼叫 concurrency::interruption_point 函式來取代 is_task_cancellation_requestedcancel_current_task

當您回應取消作業,因為它已經轉換工作與已取消的狀態時,由 cancel_current_task 呼叫是很重要的。 如果您先前傳回而不是呼叫 cancel_current_task,以完成作業轉換陳述式,而且所有數值繼續執行。

警告

不要擲回從程式碼的 task_canceled 。請改為呼叫 cancel_current_task

當處於已取消狀態的工作完成時, concurrency::task::get 方法會擲回 concurrency::task_canceled。(相反地, concurrency::task::wait 會傳回 task_status::canceled ,而且會擲回)。下列範例說明如何以工作的接續的行為。 即使前項工作已取消,以工作的接續一定要呼叫。

// task-canceled.cpp 
// compile with: /EHsc
#include <ppltasks.h>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain()
{
    auto t1 = create_task([]() -> int
    {
        // Cancel the task.
        cancel_current_task();
    });

    // Create a continuation that retrieves the value from the previous.
    auto t2 = t1.then([](task<int> t)
    {
        try
        {
            int n = t.get();
            wcout << L"The previous task returned " << n << L'.' << endl;
        }
        catch (const task_canceled& e)
        {
            wcout << L"The previous task was canceled." << endl;
        }
    });

    // Wait for all tasks to complete.
    t2.wait();
}
/* Output:
    The previous task was canceled.
*/

由於數值在繼承其前項工作語彙基元,除非建立明確的語彙基元,立即繼續進入已取消的狀態,即使前項工作仍在執行。 因此,由前項工作所擲回的任何例外狀況,在移除不會傳播至接續後配置。 移除一定會覆寫前項工作的狀態。 下列範例類似前,但說明如何以值之接續的行為。

auto t1 = create_task([]() -> int
{
    // Cancel the task.
    cancel_current_task();
});

// Create a continuation that retrieves the value from the previous.
auto t2 = t1.then([](int n)
{
    wcout << L"The previous task returned " << n << L'.' << endl;
});

try
{
    // Wait for all tasks to complete.
    t2.get();
}
catch (const task_canceled& e)
{
    wcout << L"The task was canceled." << endl;
}
/* Output:
    The task was canceled.
*/

警告

如果您不將取消語彙基元傳遞至 task 建構函式或 concurrency::create_task 函式,該工作不會移除。此外,您必須傳遞相同的取消語彙基元至另一個工作主體建立的任何巢狀工作 (也就是工作的建構函式)同時移除所有工作。

當取消語彙基元被取消時,您可能會想要執行任意程式碼。 例如,在中,如果您的使用者在使用者介面中的 [移除] 按鈕則會取消作業,您可以停用該按鈕,直到使用者啟動另一個作業。 下列範例顯示如何使用 concurrency::cancellation_token::register_callback 方法來註冊所執行的回呼函式,當取消語彙基元被取消時。

// task-cancellation-callback.cpp 
// compile with: /EHsc
#include <ppltasks.h>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain()
{
    cancellation_token_source cts;
    auto token = cts.get_token();

    // An event that is set in the cancellation callback. 
    event e;

    cancellation_token_registration cookie;
    cookie = token.register_callback([&e, token, &cookie]()
    {
        wcout << L"In cancellation callback..." << endl;
        e.set();

        // Although not required, demonstrate how to unregister  
        // the callback.
        token.deregister_callback(cookie);
    });

    wcout << L"Creating task..." << endl;

    // Create a task that waits to be canceled.
    auto t = create_task([&e]
    {
        e.wait();
    }, token);

    // Cancel the task.
    wcout << L"Canceling task..." << endl;
    cts.cancel();

    // Wait for the task to cancel.
    t.wait();

    wcout << L"Done." << endl;
}
/* Sample output:
    Creating task...
    Canceling task...
    In cancellation callback...
    Done.
*/

工作平行處理原則 (並行執行階段) 文件說明數值和以工作的接續之間的差異。 如果您不提供對接續工作的 cancellation_token 物件,會繼續以下列方式繼承自前項工作的取消語彙基元:

  • 數值繼續永遠繼承前項工作的取消語彙基元。

  • 以工作為準的的接續永遠不會繼承其前項工作語彙基元。 唯一能使以工作的接續取消的方式是傳遞一個取消語彙基元。

這些行為不會受已發生錯誤的工作影響 (即會擲回例外狀況) 。 在這種情況下,數值的接續取消;以工作的接續不會移除。

警告

一個在另一個工作產生的工作( 換句話說,巢狀工作) 將不是繼承自父工作的取消語彙基元。只有一個數值會繼承它的前項工作取消語彙基元。

提示

請使用 concurrency::cancellation_token::none 方法,當您呼叫接受 cancellation_token 物件的建構函式或函式,且您不想要作業可以被取消。

您也可以提供給取消語彙基元 task_groupstructured_task_group 物件的建構函式。 一個重要方面是子工作群組繼承這個取消語彙基元。 如需示範這個概念是使用 concurrency::run_with_cancellation_token 函式來執行呼叫 parallel_for的範例,請參閱 取消平行演算法 之後在文件。

[上方]

取消語彙基元和工作組合

concurrency::when_allconcurrency::when_any 函式,可協助您撰寫多個工作來實作一般模式。 本節將說明這些函式如何使用取消語彙基元一起使用。

當您提供取消語彙基元給任何 when_allwhen_any 函式,該函式取消,只有在那個取消語彙基元,或其中一個處於已取消狀態的參與者工作結束或擲回例外狀況。

當您未提供取消語彙基元給它時, when_all 函式會繼承組成整體作業的每個工作的取消語彙基元。 從 when_all 傳回的工作已取消,當這些語彙基元中的任一個取消時和工作尚未啟動或執行的至少一個參與者。 類似的行為,發生於其中一個工作擲回例外狀況–從 when_all 傳回的工作會立即移除具有該例外狀況。

該工作完成時,執行階段會選擇從 when_any 函式傳回的工作的取消語彙基元。 如果沒有任何參與者工作完成在完成狀態,且一或多個工作擲回例外狀況時,擲回的其中一個工作選取來完成 when_any 及其語彙基元會選取做為最後一個工作的語彙基元。 如果多個工作在完成狀態時,從 when_any 工作傳回的工作結束在完成狀態。 選取語彙基元無法在完成時移除,以便從 when_any 傳回的工作完成執行階段嘗試不會立即被取消,即使其他執行的工作可能完成在較新的。

[上方]

使用取消方法來取消平行工作

concurrency::task_group::cancelconcurrency::structured_task_group::cancel 方法會將工作群組設為已取消的狀態。 在您呼叫 cancel 之後,工作群組即不會啟動未來的工作。 cancel 方法可由多項子工作來呼叫。 工作取消後,會使 concurrency::task_group::waitconcurrency::structured_task_group::wait 方法傳回 concurrency::canceled

在工作群組遭到取消時,執行階段中每個從子工作發出的呼叫將可觸發「中斷點」(Interruption Point),而使執行階段擲回和攔截內部例外狀況類型,以取消作用中的工作。 並行執行階段並未定義特定的中斷點;中斷點可透過執行階段中的任何呼叫來產生。 執行階段必須處理它所擲回的例外狀況,以執行取消作業。 因此,請不要處理工作主體中的未知例外狀況。

如果子工作正在執行耗時的作業,且未在執行階段中發出呼叫,則必須定期檢查取消的狀態,並適時地結束作業。 下列範例說明一種判斷工作於何時取消的方式。 工作 t4 會在錯誤發生時取消父工作群組。 工作 t5 偶爾會呼叫 structured_task_group::is_canceling 方法,以檢查取消的狀態。 如果父工作群組已取消,工作 t5 顯示訊息,並結束作業。

structured_task_group tg2;

// Create a child task.
auto t4 = make_task([&] {
   // Perform work in a loop. 
   for (int i = 0; i < 1000; ++i)
   {
      // Call a function to perform work. 
      // If the work function fails, cancel the parent task 
      // and break from the loop. 
      bool succeeded = work(i);
      if (!succeeded)
      {
         tg2.cancel();
         break;
      }
   }
});

// Create a child task.
auto t5 = make_task([&] {
   // Perform work in a loop. 
   for (int i = 0; i < 1000; ++i)
   {
      // To reduce overhead, occasionally check for  
      // cancelation. 
      if ((i%100) == 0)
      {
         if (tg2.is_canceling())
         {
            wcout << L"The task was canceled." << endl;
            break;
         }
      }

      // TODO: Perform work here.
   }
});

// Run the child tasks and wait for them to finish.
tg2.run(t4);
tg2.run(t5);
tg2.wait();

此範例會在工作迴圈每反覆執行 100 次時,檢查取消的狀態。 您檢查取消狀態的頻率,取決於您所執行的工作量,以及您的工作必須多快回應取消要求。

如果您沒有父工作群組物件的存取權,請呼叫 concurrency::is_current_task_group_canceling 函式,以判斷父工作群組是否已取消。

cancel 方法只會影響子工作。 例如,如果您在平行工作樹狀結構的圖例中取消了工作群組 tg1,則樹狀結構中的所有工作 (t1t2t3t4t5) 都會受到影響。 如果您取消了巢狀工作群組 tg2,則只有 t4t5 工作會受到影響。

當您呼叫 cancel 方法時,所有的子工作群組也都會被取消。 但是,取消並不會影響平行工作樹狀結構中的工作群組的任何父代。 下列範例透過建置平行工作樹狀結構圖例加以說明。

其中的第一個範例會建立工作 t4 的工作函式,此工作是工作群組 tg2 的子系。 此工作函式會以迴圈呼叫函式 work。 如果 work 的任何呼叫失敗,工作即會取消其父工作群組。 這會導致工作群組 tg2 進入已取消的狀態,但並不會取消工作群組 tg1

auto t4 = make_task([&] {
   // Perform work in a loop. 
   for (int i = 0; i < 1000; ++i)
   {
      // Call a function to perform work. 
      // If the work function fails, cancel the parent task 
      // and break from the loop. 
      bool succeeded = work(i);
      if (!succeeded)
      {
         tg2.cancel();
         break;
      }
   }         
});

第二個範例與第一個類似,不同之處在於工作會取消工作群組 tg1。 這會影響樹狀結構中的所有工作 (t1t2t3t4t5)。

auto t4 = make_task([&] {
   // Perform work in a loop. 
   for (int i = 0; i < 1000; ++i)
   {
      // Call a function to perform work. 
      // If the work function fails, cancel all tasks in the tree. 
      bool succeeded = work(i);
      if (!succeeded)
      {
         tg1.cancel();
         break;
      }
   }   
});

structured_task_group 類別不具備執行緒安全。 因此,若有子工作呼叫其父 structured_task_group 物件的方法,將會產生未指定的行為。 此規則的例外是 structured_task_group::cancelconcurrency::structured_task_group::is_canceling 方法。 子工作可以呼叫這些方法以取消父工作群組,並檢查是否已取消。

警告

雖然您可以使用取消語彙基元取消工作群組執行以 task 物件的子系的工作,您不能使用 task_group::cancelstructured_task_group::cancel 方法會移除在工作群組中執行的 task 物件。

[上方]

使用例外狀況取消平行工作

在使用取消平行工作樹狀結構和 cancel 方法時,取消機制比例外狀況處理更有效率。 取消語彙基元和 cancel 方法來取消工作和所有子工作以由上而下的方式。 相反地,例外狀況處理則會由下而上執行,且必須在例外狀況往上傳播時個別取消每一個子工作群組。 並行執行階段的例外狀況處理主題說明並行執行階段如何使用例外狀況發出錯誤。 但並非所有的例外狀況都表示有錯誤。 例如,搜尋演算法在找到結果時,即可能取消其相關聯的工作。 但如前所述,相較於使用 cancel 方法取消平行工作,處理例外狀況顯得較沒效率。

警告

建議您只在需要時使用例外狀況取消平行工作。取消語彙基元和工作群組 cancel 方法更有效率而且比較不容易出錯。

當您在傳遞至工作群組的工作函式主體中擲回例外狀況時,執行階段會儲存該例外狀況,並將其封送處理至等候工作群組完成的內容。 與使用 cancel 方法時相同,執行階段會捨棄任何尚未啟動的工作,並且不會接受新的工作。

第三個範例與第二個類似,不同之處在於工作 t4 會擲回例外狀況,以取消工作群組 tg2。 這個範例會使用 try-catch 區塊,在工作群組 tg2 等候其子工作完成時檢查取消的狀態。 與第一個範例相同,這會使工作群組 tg2 進入已取消的狀態,但並不會取消工作群組 tg1

structured_task_group tg2;

// Create a child task.      
auto t4 = make_task([&] {
   // Perform work in a loop. 
   for (int i = 0; i < 1000; ++i)
   {
      // Call a function to perform work. 
      // If the work function fails, throw an exception to  
      // cancel the parent task. 
      bool succeeded = work(i);
      if (!succeeded)
      {
         throw exception("The task failed");
      }
   }         
});

// Create a child task.
auto t5 = make_task([&] {
   // TODO: Perform work here.
});

// Run the child tasks.
tg2.run(t4);
tg2.run(t5);

// Wait for the tasks to finish. The runtime marshals any exception 
// that occurs to the call to wait. 
try
{
   tg2.wait();
}
catch (const exception& e)
{
   wcout << e.what() << endl;
}

第四個範例會使用例外狀況處理,將整個工作樹狀結構取消。 此範例會在工作群組 tg1 等候其子工作完成時攔截例外狀況,而不會在工作群組 tg2 等候其子工作執行時進行攔截。 如同第二個範例,這會使樹狀結構中的兩個工作群組 tg1tg2 都進入已取消的狀態。

// Run the child tasks.
tg1.run(t1);
tg1.run(t2);
tg1.run(t3);   

// Wait for the tasks to finish. The runtime marshals any exception 
// that occurs to the call to wait. 
try
{
   tg1.wait();
}
catch (const exception& e)
{
   wcout << e.what() << endl;
}

由於 task_group::waitstructured_task_group::wait 方法會在子工作擲回例外狀況時擲回,因此您不會收到它們所傳回的值。

[上方]

取消平行演算法

PPL 中的平行演算法 (如 parallel_for) 建置於工作群組上。 因此,您可以使用許多相同的技術來取消平行演算法。

下列範例說明數種可取消平行演算法的方式。

下列範例會使用 run_with_cancellation_token 函式呼叫 parallel_for 演算法。 run_with_cancellation_token 函式接受取消語彙基元做為引數而同步呼叫提供的工作函式。 由於平行演算法中建立工作,會繼承父工作的取消語彙基元。 因此, parallel_for 可以回應取消。

// cancel-parallel-for.cpp 
// compile with: /EHsc
#include <ppltasks.h>
#include <iostream>
#include <sstream>

using namespace concurrency;
using namespace std;

int wmain()
{
    // Call parallel_for in the context of a cancellation token.
    cancellation_token_source cts;
    run_with_cancellation_token([&cts]() 
    {
        // Print values to the console in parallel.
        parallel_for(0, 20, [&cts](int n)
        {
            // For demonstration, cancel the overall operation  
            // when n equals 11. 
            if (n == 11)
            {
                cts.cancel();
            }
            // Otherwise, print the value. 
            else
            {
                wstringstream ss;
                ss << n << endl;
                wcout << ss.str();
            }
        });
    }, cts.get_token());
}
/* Sample output:
    15
    16
    17
    10
    0
    18
    5
*/

下列範例會使用 concurrency::structured_task_group::run_and_wait 方法呼叫 parallel_for 演算法。 structured_task_group::run_and_wait 方法會等候所提供的工作完成。 structured_task_group 物件可讓工作函式取消工作。

// To enable cancelation, call parallel_for in a task group.
structured_task_group tg;

task_group_status status = tg.run_and_wait([&] {
   parallel_for(0, 100, [&](int i) {
      // Cancel the task when i is 50. 
      if (i == 50)
      {
         tg.cancel();
      }
      else
      {
         // TODO: Perform work here.
      }
   });
});

// Print the task group status.
wcout << L"The task group status is: ";
switch (status)
{
case not_complete:
   wcout << L"not complete." << endl;
   break;
case completed:
   wcout << L"completed." << endl;
   break;
case canceled:
   wcout << L"canceled." << endl;
   break;
default:
   wcout << L"unknown." << endl;
   break;
}

這個範例產生下列輸出。

  

下列範例會使用例外狀況處理來取消 parallel_for 迴圈。 執行階段會將例外狀況封送處理至呼叫的內容。

try
{
   parallel_for(0, 100, [&](int i) {
      // Throw an exception to cancel the task when i is 50. 
      if (i == 50)
      {
         throw i;
      }
      else
      {
         // TODO: Perform work here.
      }
   });
}
catch (int n)
{
   wcout << L"Caught " << n << endl;
}

這個範例產生下列輸出。

  

下列範例會使用布林值旗標協調 parallel_for 迴圈中的取消。 由於此範例未使用 cancel 方法或例外狀況處理來取消整體的工作集,所以每項工作都會執行。 因此,這項技術的計算額外負荷可能會高於取消機制。

// Create a Boolean flag to coordinate cancelation. 
bool canceled = false;

parallel_for(0, 100, [&](int i) {
   // For illustration, set the flag to cancel the task when i is 50. 
   if (i == 50)
   {
      canceled = true;
   }

   // Perform work if the task is not canceled. 
   if (!canceled)
   {
      // TODO: Perform work here.
   }
});

每個取消方法都有其優缺點。 請選擇符合您特定需求的方法。

[上方]

不使用取消的時機

如果相關工作的每個群組成員皆可適時結束,則適合使用取消。 但在某些情況下,取消可能不適用於您的應用程式。 例如,由於取消工作是合作式作業,因此如果有任何個別工作遭到封鎖,整體的工作集即不會取消。 例如,如果某項工作尚未啟動,但它將另一個作用中工作解除封鎖,則在工作群組取消時,此工作將不會啟動。 這可能會造成應用程式中發生死結。 另一個不適合使用取消的例子是當工作已取消,但其子工作正在執行重要的作業,例如釋放資源。 因為父工作取消時,整組工作都會被取消,所以不會執行該作業。 如需說明這些重點的範例,請參閱<平行模式程式庫中的最佳作法>主題的了解取消和例外狀況處理對物件解構的影響一節。

[上方]

相關主題

標題

說明

如何:使用取消來中斷平行迴圈

說明如何使用取消來實作平行搜尋演算法。

如何:使用例外狀況處理來中斷平行迴圈

說明如何使用 task_group 類別撰寫基本樹狀結構的搜尋演算法。

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

說明執行階段如何處理由工作群組、輕量型工作和非同步代理程式所擲回的例外狀況,以及如何回應您應用程式中的例外狀況。

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

說明工作與工作群組之間的相關性,以及如何在您的應用程式中使用非結構化與結構化的工作。

平行演算法

說明可對資料集合執行並行工作的平行演算法

平行模式程式庫 (PPL)

提供平行模式程式庫的概觀。

參考資料

task 類別 (並行執行階段)

cancellation_token_source 類別

cancellation_token 類別

task_group 類別

structured_task_group 類別

parallel_for 函式