共用方式為


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

在並行運行時間中,工作是一個工作單位,它會執行特定工作,而且通常會與其他工作平行執行。 工作可以分解成其他更精細的工作,這些工作會組織成 工作組

當您撰寫非同步程式碼,並想要在非同步作業完成之後執行一些作業時,您可以使用工作。 例如,您可以使用工作以異步方式從檔案讀取,然後使用另一個 工作,也就是本檔中稍後說明的接續工作,以在數據可供使用之後處理數據。 反過來說,您可以使用工作群組將平行工作分解成較小的片段。 例如,假設您擁有可將剩餘工作分成兩個分割的遞迴演算法, 您就可以使用工作群組同時執行這些分割,然後等待分割的工作完成。

提示

當您想要以平行方式將相同的例程套用至集合的每個元素時,請使用並行演算法,例如 concurrency::p arallel_for,而不是工作或工作組。 如需平行演算法的詳細資訊,請參閱 平行演算法

重點

  • 當您以傳址方式將變數傳遞給 lambda 運算式時,必須保證該變數的存留期會一直持續到工作完成。

  • 當您撰寫異步程式代碼時,請使用工作( 並行::task 類別)。 Task 類別使用 Windows 執行緒集區做為其排程器,而不使用並行執行階段。

  • 當您想要將平行工作分解成較小的部分時,請使用工作組 ( concurrency::task_group 類別或 concurrency::p arallel_invoke 演算法),然後等候這些較小的片段完成。

  • 使用 concurrency::task::then 方法來建立接續。 接 是在另一個工作完成之後以異步方式執行的工作。 您可以連接任意數目的接續,形成非同步工作鏈結。

  • 以工作為基礎的接續一定會排定在前項工作完成時執行,即使當前項工作取消或擲回例外狀況時亦然。

  • 使用 concurrency::when_all 建立工作,以在一組工作的每個成員完成之後完成。 使用 concurrency::when_any 建立一組工作完成之後完成的工作。

  • 工作和工作群組可以參與平行模式程式庫 (PPL) 取消機制。 如需詳細資訊,請參閱 PPL中的取消。

  • 若要瞭解運行時間如何處理工作和工作組擲回的例外狀況,請參閱 例外狀況處理

本文內容

使用 Lambda 運算式

由於 Lambda 運算式的語法簡潔,因此 Lambda 運算式是定義工作和工作群組所執行工作的常見方式。 以下有一些使用提示:

  • 工作通常是在背景執行緒上執行,因此當您擷取 Lambda 運算式中的變數時請注意物件存留期。 當您以傳值方式擷取變數時,會在 Lambda 主體中建立該變數的複本。 當您以傳址方式擷取時,則不會建立複本。 因此,請確定您以傳址方式擷取的任何變數,存留期比使用它的工作還長。

  • 當您將 Lambda 表達式傳遞至工作時,請勿依參考擷取堆疊上配置的變數。

  • 明確說明您在 Lambda 運算式中擷取的變數,以便識別您要依值擷取的內容與傳址。 因此我們建議您不要針對 Lambda 運算式使用 [=][&] 選項。

常見的模式是接續鏈結中的一項工作指派給變數時,另一項工作讀取該變數。 您無法依值擷取,因為每個接續工作都會保存變數的不同複本。 針對堆疊配置的變數,您也無法依參考擷取,因為變數可能不再有效。

若要解決此問題,請使用智慧型指標,例如 std::shared_ptr,以包裝變數,並依值傳遞智慧型手機。 如此一來,便可以指派和讀取基礎物件,且存留期會比使用它的工作長。 即使變數是 Windows 執行階段物件的指標或參考計數的控制代碼 (^),也請使用這項技術。 以下是基本範例:

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

using namespace concurrency;
using namespace std;

task<wstring> write_to_string()
{
    // Create a shared pointer to a string that is 
    // assigned to and read by multiple tasks.
    // By using a shared pointer, the string outlives
    // the tasks, which can run in the background after
    // this function exits.
    auto s = make_shared<wstring>(L"Value 1");

    return create_task([s] 
    {
        // Print the current value.
        wcout << L"Current value: " << *s << endl;
        // Assign to a new value.
        *s = L"Value 2";

    }).then([s] 
    {
        // Print the current value.
        wcout << L"Current value: " << *s << endl;
        // Assign to a new value and return the string.
        *s = L"Value 3";
        return *s;
    });
}

int wmain()
{
    // Create a chain of tasks that work with a string.
    auto t = write_to_string();

    // Wait for the tasks to finish and print the result.
    wcout << L"Final value: " << t.get() << endl;
}

/* Output:
    Current value: Value 1
    Current value: Value 2
    Final value: Value 3
*/

如需 Lambda 運算式的詳細資訊,請參閱 Lambda 運算式

工作類別

您可以使用 並行::task 類別,將工作撰寫成一組相依作業。 這個組合模型是由接續的概念所支援。 接續可讓程式代碼在上一個或 項工作完成時執行。 前項工作的結果會傳遞做為一或多個接續工作的輸入。 當前項工作完成時,等候它的任何接續工作都會排定執行。 每個接續工作會收到一份前項工作之結果的複本。 這些接續工作也可能依次成為其他接續的前項工作,藉此建立工作鏈結。 接續可協助您建立任意長度的工作鏈結,這些工作之間有特定的相依性。 此外,工作可以在工作啟動之前參與取消,或是在工作執行時以合作的方式參與取消。 如需此取消模型的詳細資訊,請參閱 PPL 中的取消。

task 是範本類別。 類型參數 T 是工作所產生之結果的類型。 如果工作不會傳回值,此類型可以是 voidT 無法使用 const 修飾詞。

當您建立工作時,會提供執行 工作主體的工作函 式。 此工作函式的形式會是 Lambda 函式、函式指標或函式物件。 若要等候工作完成而不取得結果,請呼叫 concurrency::task::wait 方法。 task::wait方法會傳回並行::task_status值,描述工作是否已完成或取消。 若要取得工作的結果,請呼叫 concurrency::task::get 方法。 這個方法會呼叫 task::wait 等候工作完成,因此會阻擋目前執行緒的執行,直到得到結果為止。

下列範例示範如何建立工作、等候其結果,並顯示其值。 這份文件中的範例使用 Lambda 函式,因為其提供更簡潔的語法。 不過,當您使用工作時,您也可以使用函式指標和函式物件。

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

using namespace concurrency;
using namespace std;

int wmain()
{
    // Create a task.
    task<int> t([]()
    {
        return 42;
    });

    // In this example, you don't necessarily need to call wait() because
    // the call to get() also waits for the result.
    t.wait();

    // Print the result.
    wcout << t.get() << endl;
}

/* Output:
    42
*/

當您使用 並行::create_task 函式時,您可以使用 auto 關鍵詞,而不是宣告類型。 例如,請考慮這段可建立與列印識別矩陣的程式碼:

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

using namespace concurrency;
using namespace std;

int wmain()
{
    task<array<array<int, 10>, 10>> create_identity_matrix([]
    {
        array<array<int, 10>, 10> matrix;
        int row = 0;
        for_each(begin(matrix), end(matrix), [&row](array<int, 10>& matrixRow) 
        {
            fill(begin(matrixRow), end(matrixRow), 0);
            matrixRow[row] = 1;
            row++;
        });
        return matrix;
    });

    auto print_matrix = create_identity_matrix.then([](array<array<int, 10>, 10> matrix)
    {
        for_each(begin(matrix), end(matrix), [](array<int, 10>& matrixRow) 
        {
            wstring comma;
            for_each(begin(matrixRow), end(matrixRow), [&comma](int n) 
            {
                wcout << comma << n;
                comma = L", ";
            });
            wcout << endl;
        });
    });

    print_matrix.wait();
}
/* Output:
    1, 0, 0, 0, 0, 0, 0, 0, 0, 0
    0, 1, 0, 0, 0, 0, 0, 0, 0, 0
    0, 0, 1, 0, 0, 0, 0, 0, 0, 0
    0, 0, 0, 1, 0, 0, 0, 0, 0, 0
    0, 0, 0, 0, 1, 0, 0, 0, 0, 0
    0, 0, 0, 0, 0, 1, 0, 0, 0, 0
    0, 0, 0, 0, 0, 0, 1, 0, 0, 0
    0, 0, 0, 0, 0, 0, 0, 1, 0, 0
    0, 0, 0, 0, 0, 0, 0, 0, 1, 0
    0, 0, 0, 0, 0, 0, 0, 0, 0, 1
*/

您可以使用 create_task 函式來建立對等的作業。

auto create_identity_matrix = create_task([]
{
    array<array<int, 10>, 10> matrix;
    int row = 0;
    for_each(begin(matrix), end(matrix), [&row](array<int, 10>& matrixRow) 
    {
        fill(begin(matrixRow), end(matrixRow), 0);
        matrixRow[row] = 1;
        row++;
    });
    return matrix;
});

如果在工作執行期間擲回例外狀況,執行階段會在後續呼叫 task::gettask::wait,或以工作為基礎的接續時,封送處理該例外狀況。 如需工作例外狀況處理機制的詳細資訊,請參閱 例外狀況處理

如需使用 task並行::task_completion_event、取消的範例,請參閱 逐步解說:使用工作和 XML HTTP 要求進行連線。 (本文件稍後會說明 task_completion_event 類別。)

提示

若要瞭解 UWP app 中工作特有的詳細數據,請參閱 UWP App C++ 中的 異步程序設計,以及 針對 UWP App 在 C++中建立異步操作。

接續工作

在非同步程式設計中,非同步作業完成時叫用第二個作業,並將資料傳遞給它,是非常普遍的。 傳統上,這項作業使用回呼方法完成。 在並行運行時間中,接續工作提供相同的功能。 接續工作(也稱為接續)是由另一個工作叫用的異步工作,也就是前項完成時稱為前項的工作。 藉由使用接續,您可以:

  • 將資料從前項傳遞至接續。

  • 指定叫用或不叫用接續的精確條件。

  • 在接續開始之前或在執行時以合作的方式取消接續。

  • 提供有關該如何排定接續的提示。 (這僅適用於 通用 Windows 平台 (UWP) 應用程式。 如需詳細資訊,請參閱 在適用於UWPApps的 C++ 中建立異步操作。)

  • 從相同前項叫用多個接續。

  • 當多個前項中的所有或任何一項完成時,叫用一個接續。

  • 將接續一個接著一個地鏈結起來,可至任意長度。

  • 使用接續處理前項所擲回的例外狀況。

這些功能可讓您在第一個工作完成時執行一或多個工作。 例如,您可以建立接續,在第一個工作從磁碟讀取檔案之後將其壓縮。

下列範例會修改前一個範例,以使用 concurrency::task::then 方法來排程接續,以在前項工作可用時列印其值。

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

using namespace concurrency;
using namespace std;

int wmain()
{
    auto t = create_task([]() -> int
    {
        return 42;
    });

    t.then([](int result)
    {
        wcout << result << endl;
    }).wait();

    // Alternatively, you can chain the tasks directly and
    // eliminate the local variable.
    /*create_task([]() -> int
    {
        return 42;
    }).then([](int result)
    {
        wcout << result << endl;
    }).wait();*/
}

/* Output:
    42
*/

您可以將工作鏈結及巢狀化至任何長度。 工作也可以有多個接續。 下列範例說明基本的接續鏈結,其可將前一個工作的值遞增三次。

// continuation-chain.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain()
{
    auto t = create_task([]() -> int
    { 
        return 0;
    });
    
    // Create a lambda that increments its input value.
    auto increment = [](int n) { return n + 1; };

    // Run a chain of continuations and print the result.
    int result = t.then(increment).then(increment).then(increment).get();
    wcout << result << endl;
}

/* Output:
    3
*/

接續也可以傳回另一項工作。 如果沒有取消,則這項工作會在後續的接續之前執行。 這項技術稱為 異步解除包裝。 非同步解除包裝適用於您想要在背景中執行其他工作,但不是想讓目前的工作阻擋目前的執行緒時。 (這在UWP app 中很常見,其中接續可以在UI線程上執行)。 下列範例顯示三個工作。 第一項工作會傳回另一項在接續工作之前執行的工作。

// async-unwrapping.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain()
{
    auto t = create_task([]()
    {
        wcout << L"Task A" << endl;

        // Create an inner task that runs before any continuation
        // of the outer task.
        return create_task([]()
        {
            wcout << L"Task B" << endl;
        });
    });
  
    // Run and wait for a continuation of the outer task.
    t.then([]()
    {
        wcout << L"Task C" << endl;
    }).wait();
}

/* Output:
    Task A
    Task B
    Task C
*/

重要

當工作的接續傳回型別 N 的巢狀工作時,產生的工作類型為 N,而非 task<N>,並且會在巢狀工作完成時完成。 換句話說,接續會執行巢狀工作的解除包裝。

以值為基礎的與以工作為基礎的接續

假如有 task 物件,其傳回型別是 T,則您可以提供類型 Ttask<T> 的值給其接續工作。 接受型別 T接續稱為以值為基礎的接續。 以值為基礎的接續會排定在前項工作完成且沒有錯誤也未取消時執行。 採用型 task<T> 別做為其參數的接續稱為工作 型接續。 以工作為基礎的接續一定會排定在前項工作完成時執行,即使當前項工作取消或擲回例外狀況時亦然。 接著您便可以呼叫 task::get 取得前項工作的結果。 如果前項工作已取消, task::get 則會 擲回並行::task_canceled。 如果前項工作擲回例外狀況,task::get 會重新擲回該例外狀況。 以工作為基礎的接續在其前項工作取消時,不會標示為已取消。

撰寫工作

本節描述 並行::when_all並行::when_any 函式,可協助您撰寫多個工作來實作一般模式。

when_all函式

when_all 函式會產生在一組工作完成之後完成的工作。 此函式會傳回 std::vector 物件,其中包含集合中每個工作的結果。 下列基本範例使用 when_all 來建立代表其他三個工作已完成的工作。

// join-tasks.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <array>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain()
{
    // Start multiple tasks.
    array<task<void>, 3> tasks = 
    {
        create_task([] { wcout << L"Hello from taskA." << endl; }),
        create_task([] { wcout << L"Hello from taskB." << endl; }),
        create_task([] { wcout << L"Hello from taskC." << endl; })
    };

    auto joinTask = when_all(begin(tasks), end(tasks));

    // Print a message from the joining thread.
    wcout << L"Hello from the joining thread." << endl;

    // Wait for the tasks to finish.
    joinTask.wait();
}

/* Sample output:
    Hello from the joining thread.
    Hello from taskA.
    Hello from taskC.
    Hello from taskB.
*/

注意

您傳遞給 when_all 的工作必須一致。 也就是說,它們必須全部傳回相同的類型。

您也可以使用 && 語法來產生在一組工作完成之後完成的工作,如下列範例所示。

auto t = t1 && t2; // same as when_all

將接續與 when_all 搭配使用是常見的作法,用來在一組工作完成後執行動作。 下列範例會修改前一個範例,列印皆會產生 int 結果的三項工作的總和。

// Start multiple tasks.
array<task<int>, 3> tasks =
{
    create_task([]() -> int { return 88; }),
    create_task([]() -> int { return 42; }),
    create_task([]() -> int { return 99; })
};

auto joinTask = when_all(begin(tasks), end(tasks)).then([](vector<int> results)
{
    wcout << L"The sum is " 
          << accumulate(begin(results), end(results), 0)
          << L'.' << endl;
});

// Print a message from the joining thread.
wcout << L"Hello from the joining thread." << endl;

// Wait for the tasks to finish.
joinTask.wait();

/* Output:
    Hello from the joining thread.
    The sum is 229.
*/

在此範例中,您也可以指定 task<vector<int>> 來產生以工作為基礎的接續。

如果一組工作中有任何工作取消或擲回例外狀況,when_all 會立即完成,並且不會等待其餘的工作完成。 如果擲回例外狀況,執行階段會在您對 when_all 傳回的工作物件呼叫 task::gettask::wait 時重新擲回例外狀況。 如果有多個工作擲回例外狀況,執行階段會選擇其中之一。 因此,請確保您在所有工作完成之後,觀察到所有例外狀況;未處理的工作例外狀況會導致應用程式終止。

以下是一個公用程式函式,可讓您用來確保程序觀察所有例外狀況。 針對提供的範圍內每一項工作,observe_all_exceptions 會觸發重新擲回發生的任何例外狀況,接著抑制該例外狀況。

// Observes all exceptions that occurred in all tasks in the given range.
template<class T, class InIt> 
void observe_all_exceptions(InIt first, InIt last) 
{
    std::for_each(first, last, [](concurrency::task<T> t)
    {
        t.then([](concurrency::task<T> previousTask)
        {
            try
            {
                previousTask.get();
            }
            // Although you could catch (...), this demonstrates how to catch specific exceptions. Your app
            // might handle different exception types in different ways.
            catch (Platform::Exception^)
            {
                // Swallow the exception.
            }
            catch (const std::exception&)
            {
                // Swallow the exception.
            }
        });
    });
}

請考慮使用 C++ 和 XAML 的 UWP 應用程式,並將一組檔案寫入磁碟。 下列範例顯示如何使用 when_allobserve_all_exceptions 以確保程式會觀察到所有例外狀況。

// Writes content to files in the provided storage folder.
// The first element in each pair is the file name. The second element holds the file contents.
task<void> MainPage::WriteFilesAsync(StorageFolder^ folder, const vector<pair<String^, String^>>& fileContents)
{
    // For each file, create a task chain that creates the file and then writes content to it. Then add the task chain to a vector of tasks.
    vector<task<void>> tasks;
    for (auto fileContent : fileContents)
    {
        auto fileName = fileContent.first;
        auto content = fileContent.second;

        // Create the file. The CreationCollisionOption::FailIfExists flag specifies to fail if the file already exists.
        tasks.emplace_back(create_task(folder->CreateFileAsync(fileName, CreationCollisionOption::FailIfExists)).then([content](StorageFile^ file)
        {
            // Write its contents.
            return create_task(FileIO::WriteTextAsync(file, content));
        }));
    }

    // When all tasks finish, create a continuation task that observes any exceptions that occurred.
    return when_all(begin(tasks), end(tasks)).then([tasks](task<void> previousTask)
    {
        task_status status = completed;
        try
        {
            status = previousTask.wait();
        }
        catch (COMException^ e)
        {
            // We'll handle the specific errors below.
        }
        // TODO: If other exception types might happen, add catch handlers here.

        // Ensure that we observe all exceptions.
        observe_all_exceptions<void>(begin(tasks), end(tasks));

        // Cancel any continuations that occur after this task if any previous task was canceled.
        // Although cancellation is not part of this example, we recommend this pattern for cases that do.
        if (status == canceled)
        {
            cancel_current_task();
        }
    });
}
執行此範例
  1. 在 MainPage.xaml 中加入 Button 控制項。
<Button x:Name="Button1" Click="Button_Click">Write files</Button>
  1. 在 MainPage.xaml.h中,將這些向前宣告加入 MainPage 類別宣告的 private 區段。
void Button_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e);
concurrency::task<void> WriteFilesAsync(Windows::Storage::StorageFolder^ folder, const std::vector<std::pair<Platform::String^, Platform::String^>>& fileContents);
  1. 在 MainPage.xaml.cpp 中實作 Button_Click 事件處理常式。
// A button click handler that demonstrates the scenario.
void MainPage::Button_Click(Object^ sender, RoutedEventArgs^ e)
{
    // In this example, the same file name is specified two times. WriteFilesAsync fails if one of the files already exists.
    vector<pair<String^, String^>> fileContents;
    fileContents.emplace_back(make_pair(ref new String(L"file1.txt"), ref new String(L"Contents of file 1")));
    fileContents.emplace_back(make_pair(ref new String(L"file2.txt"), ref new String(L"Contents of file 2")));
    fileContents.emplace_back(make_pair(ref new String(L"file1.txt"), ref new String(L"Contents of file 3")));

    Button1->IsEnabled = false; // Disable the button during the operation.
    WriteFilesAsync(ApplicationData::Current->TemporaryFolder, fileContents).then([this](task<void> previousTask)
    {
        try
        {
            previousTask.get();
        }
        // Although cancellation is not part of this example, we recommend this pattern for cases that do.
        catch (const task_canceled&)
        {
            // Your app might show a message to the user, or handle the error in some other way.
        }

        Button1->IsEnabled = true; // Enable the button.
    });
}
  1. 在 MainPage.xaml.cpp 中實作 WriteFilesAsync,如範例所示。

提示

when_all 是未封鎖的函式,會產生 task 做為其結果。 不同於 task::wait,在 ASTA (Application STA) 線程上的 UWP 應用程式中呼叫此函式是安全的。

when_any函式

when_any 函式會產生在一組工作的第一項工作完成之後完成的工作。 此函式會傳 回 std::p air 物件,其中包含已完成工作的結果,以及該工作在集合中的索引。

when_any 函式在下列情節中特別有用:

  • 重複的作業。 請考慮能夠以多種方式執行的演算法或作業。 您可以使用 when_any 函式選擇先完成的作業,然後取消其餘作業。

  • 交錯作業。 您可以啟動所有必須完成的多個作業,並使用 when_any 函式在每個作業完成時處理結果。 完成作業後,您可以開始一個或多個其他工作。

  • 已節流的作業。 您可以使用 when_any 函式來限制並行作業的數目,以擴充前一個情節。

  • 過期的作業。 您可以使用 when_any 函式,選取一或多個工作,還是在指定時間之後完成的工作。

如同 when_all,使用具有 when_any 的接續在一組工作中的第一項工作完成時執行動作是很常見的方式。 下列的基本範例使用 when_any 來建立在其他三個工作中第一個工作完成時完成的工作。

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

using namespace concurrency;
using namespace std;

int wmain()
{
    // Start multiple tasks.
    array<task<int>, 3> tasks = {
        create_task([]() -> int { return 88; }),
        create_task([]() -> int { return 42; }),
        create_task([]() -> int { return 99; })
    };

    // Select the first to finish.
    when_any(begin(tasks), end(tasks)).then([](pair<int, size_t> result)
    {
        wcout << "First task to finish returns "
              << result.first
              << L" and has index "
              << result.second
              << L'.' << endl;
    }).wait();
}

/* Sample output:
    First task to finish returns 42 and has index 1.
*/

在此範例中,您也可以指定 task<pair<int, size_t>> 來產生以工作為基礎的接續。

注意

如同 when_all,您將傳遞給 when_any 的工作必須全部傳回相同的類型。

您也可以使用 || 語法來產生工作,在一組工作中的第一項工作完成之後完成,如下列範例所示。

auto t = t1 || t2; // same as when_any

提示

和一 when_all樣, when_any 是非封鎖的,而且可以安全地在 ASTA 線程上的 UWP 應用程式中呼叫。

延遲的工作執行

有時必須要延遲工作的執行,直到滿足條件為止,或者啟動工作以回應外部事件。 例如在非同步程式設計中,您可能必須啟動工作以回應 I/O 完成事件。

完成這項作業的兩種方式是使用接續或啟動工作,並在工作的工作函式內等候事件。 然而,在某些情況中無法使用這些方法。 例如,若要建立接續,您必須要有前項工作。 不過,如果您沒有前項工作,您可以在工作完成事件可用時,將該完成事件鏈結至前項工作。 此外,因為等待中的工作也會阻擋執行緒,您可以使用工作完成事件在非同步作業完成時執行工作,並藉此釋放執行緒。

concurrency::task_completion_event 類別有助於簡化這類工作組合。 就像 task 類別,類型參數 T 是工作所產生之結果的類型。 如果工作不會傳回值,此類型可以是 voidT 無法使用 const 修飾詞。 一般而言,會提供 task_completion_event 物件給執行緒或工作,在物件的值可用時對其發出通知。 同時,一或多個工作會設定為該事件的接聽程式。 設定事件之後,接聽程式工作便會完成,而其接續工作會排定繼續執行。

如需使用 task_completion_event 來實作延遲後完成工作的範例,請參閱 如何:建立在延遲之後完成的工作。

工作群組

工作組會組織工作的集合。 工作群組將工作推送至竊取工作的佇列。 排程器將工作從這個佇列移除,並在可用的運算資源上執行。 將工作加入工作群組之後,您可以等待所有工作完成,或取消尚未開始的工作。

PPL 會使用 並行::task_group並行::structured_task_group 類別來表示工作組,以及 並行::task_handle 類別來代表在這些群組中執行的工作。 task_handle 類別會封裝執行工作的程式碼。 就像 task 類別,此工作函式的形式會是 Lambda 函式、函式指標或函式物件。 您通常不需要直接使用 task_handle 物件。 相反地,您會將工作函式傳遞至工作群組,而工作群組會建立和管理 task_handle 物件。

PPL 會將工作組分成這兩個類別: 非結構化工作組結構化工作組。 PPL 使用 task_group 類別來代表非結構化工作群組,並使用 structured_task_group 類別來代表結構化工作群組。

重要

PPL 也會定義 concurrency::p arallel_invoke 演算法,該演算法會使用 structured_task_group 類別平行執行一組工作。 由於 parallel_invoke 演算法的語法更簡潔,如果可以的話,我們建議您使用它,而不要使用 structured_task_group 類別。 平行演算法主題會更詳細地parallel_invoke說明。

當您有數個想要同時執行的獨立工作,而且必須等候所有工作完成才能繼續時,請使用 parallel_invoke。 這項技術通常稱為 分支和聯 結平行處理原則。 當您有數個想要同時執行的獨立工作,但是想要等候工作晚點才完成時,請使用 task_group。 例如,您可以將工作加入 task_group 物件,並等候工作在另一個函式或另一個執行緒中完成。

工作群組支援取消的概念。 取消可讓您通知所有作用中的工作,您想要取消整體作業。 取消也會讓尚未開始的工作無法開始。 如需取消的詳細資訊,請參閱 PPL 中的取消。

執行階段也提供例外狀況處理模型,可讓您從工作擲回例外狀況,並在等候相關聯的工作群組完成時,處理該例外狀況。 如需此例外狀況處理模型的詳細資訊,請參閱 例外狀況處理

比較task_group與structured_task_group

雖然我們建議您使用 task_groupparallel_invoke,而不要使用 structured_task_group 類別,但在某些情況下您會想要使用 structured_task_group,例如您撰寫平行處理演算法 (可執行數目可變的工作或要求取消的支援) 時。 本章節將說明 task_groupstructured_task_group 類別之間的差異。

task_group 類別是安全執行緒。 因此,您可以從多個執行緒將工作加入 task_group 物件,並從多個執行緒等候或取消 task_group 物件。 structured_task_group 物件的建構和解構必須出現在相同的語彙範圍中。 此外,structured_task_group 物件上的所有作業必須在同一個執行緒上進行。 此規則的例外狀況是 concurrency::structured_task_group::cancelconcurrency::structured_task_group::is_canceling 方法。 子工作可以呼叫這些方法來取消父工作群組,或在任何時候檢查取消。

呼叫 concurrency::task_group::waitconcurrency::task_group::run_and_wait 方法之後,您可以在 物件上task_group執行其他工作。 相反地,如果您在呼叫 concurrency::structured_task_group::waitconcurrency::structured_task_group::run_and_wait 方法之後,在 對象上structured_task_group執行其他工作,則行為是未定義的。

因為 structured_task_group 類別不會同步處理多個執行緒,所以其執行額外負荷比 task_group 類別少。 因此,如果您的問題並不需要從多個執行緒排定工作,而且您無法使用 parallel_invoke 演算法,structured_task_group 類別便可以協助您撰寫效能更好的程式碼。

如果您在一個 structured_task_group 物件內使用另一個 structured_task_group 物件,內部物件必須在外部物件完成之前完成與終結。 task_group 類別不需要巢狀工作群組在外部群組完成之前完成。

非結構化工作群組和結構化工作群組處理工作控制代碼的方式不同。 您可以直接將工作函式傳遞給 task_group 物件;task_group 物件會建立和管理您的工作控制代碼。 structured_task_group 類別會要求您管理每一項工作的 task_handle 物件。 每個 task_handle 物件都必須在其關聯之 structured_task_group 物件的整個存留期間維持有效。 使用 concurrency::make_task 函式來建立task_handle物件,如下列基本範例所示:

// make-task-structure.cpp
// compile with: /EHsc
#include <ppl.h>

using namespace concurrency;

int wmain()
{
   // Use the make_task function to define several tasks.
   auto task1 = make_task([] { /*TODO: Define the task body.*/ });
   auto task2 = make_task([] { /*TODO: Define the task body.*/ });
   auto task3 = make_task([] { /*TODO: Define the task body.*/ });

   // Create a structured task group and run the tasks concurrently.

   structured_task_group tasks;

   tasks.run(task1);
   tasks.run(task2);
   tasks.run_and_wait(task3);
}

若要管理工作句柄,如果您有可變的工作數目,請使用堆棧配置例程,例如 _malloca 或容器類別,例如 std::vector

task_groupstructured_task_group 都支援取消。 如需取消的詳細資訊,請參閱 PPL 中的取消。

範例

下列基本範例顯示如何使用工作群組。 這個範例會使用 parallel_invoke 演算法來同時執行兩個工作。 每個工作會將子工作加入 task_group 物件。 請注意,task_group 類別允許多個工作同時對其加入工作。

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

using namespace concurrency;
using namespace std;

// Prints a message to the console.
template<typename T>
void print_message(T t)
{
   wstringstream ss;
   ss << L"Message from task: " << t << endl;
   wcout << ss.str(); 
}

int wmain()
{  
   // A task_group object that can be used from multiple threads.
   task_group tasks;

   // Concurrently add several tasks to the task_group object.
   parallel_invoke(
      [&] {
         // Add a few tasks to the task_group object.
         tasks.run([] { print_message(L"Hello"); });
         tasks.run([] { print_message(42); });
      },
      [&] {
         // Add one additional task to the task_group object.
         tasks.run([] { print_message(3.14); });
      }
   );

   // Wait for all tasks to finish.
   tasks.wait();
}

以下是此範例的範例輸出:

Message from task: Hello
Message from task: 3.14
Message from task: 42

因為 parallel_invoke 演算法同時執行工作,所以輸出訊息的順序可能有所不同。

如需示範如何使用 parallel_invoke 演算法的完整範例,請參閱 如何:使用parallel_invoke撰寫平行排序例程如何:使用parallel_invoke來執行平行作業。 如需使用 task_group 類別實作異步未來的完整範例,請參閱 逐步解說:實作未來

穩固程式設計

請確定當您使用工作、工作群組和平行演算法時,了解取消和例外狀況處理的角色。 例如,在平行工作的樹狀中,已取消的工作會導致子工作無法執行。 如果其中一項子工作執行的作業,對您的應用程式很重要,例如釋放資源,這可能會造成問題。 此外,如果子工作擲回例外狀況,該例外狀況可能會透過物件解構函式散佈,並在應用程式中導致未定義的行為。 如需說明這些點的範例,請參閱 平行模式連結庫中最佳做法中的<瞭解取消和例外狀況處理如何影響物件解構 >一節。 如需PPL中取消和例外狀況處理模型的詳細資訊,請參閱 取消例外狀況處理

標題 描述
如何:使用 parallel_invoke 撰寫平行排序常式 顯示如何使用 parallel_invoke 演算法,以改善 bitonic 排序演算法的效能。
如何:使用 parallel_invoke 執行平行作業 顯示如何使用 parallel_invoke 演算法,以改善在共用資料來源上執行多個作業的程式效能。
如何:建立在延遲之後才會完成的工作 示範如何使用taskcancellation_token_sourcecancellation_tokentask_completion_event 類別來建立延遲之後完成的工作。
逐步解說:實作未來 顯示如何將並行執行階段中現有功能結合,成為可完成更多事項的功能。
平行模式程式庫 (PPL) 描述 PPL,其提供開發並行處理應用程式的命令式程式設計模型。

參考

task 類別 (並行執行階段)

task_completion_event 類別

when_all函式

when_any函式

task_group 類別

parallel_invoke函式

structured_task_group 類別