작업 병렬 처리(동시성 런타임)Task Parallelism (Concurrency Runtime)

동시성 런타임에서 작업 은 특정 작업을 수행 하 고 일반적으로 다른 작업과 병렬로 실행 되는 작업 단위입니다.In the Concurrency Runtime, a task is a unit of work that performs a specific job and typically runs in parallel with other tasks. 작업은 작업 그룹으로 구성 된 보다 세분화 된 추가 작업으로 분해할 수 있습니다.A task can be decomposed into additional, more fine-grained tasks that are organized into a task group.

비동기 코드를 작성할 때 비동기 작업이 완료된 후 일부 작업이 발생되도록 하려는 경우 작업을 사용합니다.You use tasks when you write asynchronous code and want some operation to occur after the asynchronous operation completes. 예를 들어 작업을 사용 하 여 파일에서 비동기적으로 읽은 후 다른 작업 (이 문서의 뒷부분에서 설명 하는 연속 작업)을 사용 하 여 데이터를 사용할 수 있게 되 면 해당 데이터를 처리할 수 있습니다.For example, you could use a task to asynchronously read from a file and then use another task—a continuation task, which is explained later in this document—to process the data after it becomes available. 반대로 작업 그룹을 사용하여 병렬 작업을 더 작은 부분으로 분해할 수 있습니다.Conversely, you can use tasks groups to decompose parallel work into smaller pieces. 예를 들어 남은 작업을 두 파티션으로 분할하는 재귀 알고리즘이 있다고 가정합니다.For example, suppose you have a recursive algorithm that divides the remaining work into two partitions. 작업 그룹을 사용하여 이러한 파티션을 동시에 실행한 다음 분할된 작업이 완료될 때까지 기다릴 수 있습니다.You can use task groups to run these partitions concurrently, and then wait for the divided work to complete.

컬렉션의 모든 요소에 동일한 루틴을 병렬로 적용 하려면 작업 또는 작업 그룹 대신 동시성::p arallel_for와 같은 병렬 알고리즘을 사용 합니다.When you want to apply the same routine to every element of a collection in parallel, use a parallel algorithm, such as concurrency::parallel_for, instead of a task or task group. 병렬 알고리즘에 대 한 자세한 내용은 병렬 알고리즘을 참조 하세요.For more information about parallel algorithms, see Parallel Algorithms.

요점Key Points

  • 변수를 참조로 람다 식에 전달하는 경우 작업이 완료될 때까지 해당 변수의 수명이 유지되도록 보장해야 합니다.When you pass variables to a lambda expression by reference, you must guarantee that the lifetime of that variable persists until the task finishes.

  • 비동기 코드를 작성할 때 작업 ( concurrency:: task 클래스)을 사용 합니다.Use tasks (the concurrency::task class) when you write asynchronous code. 작업 클래스는 동시성 런타임이 아닌 Windows 스레드 풀을 해당 스케줄러로 사용합니다.The task class uses the Windows ThreadPool as its scheduler, not the Concurrency Runtime.

  • 병렬 작업을 더 작은 부분으로 분해 한 다음 더 작은 부분이 완료 될 때까지 대기 하려면 작업 그룹 ( concurrency:: task_group 클래스 또는 concurrency::p arallel_invoke 알고리즘)을 사용 합니다.Use task groups (the concurrency::task_group class or the concurrency::parallel_invoke algorithm) when you want to decompose parallel work into smaller pieces and then wait for those smaller pieces to complete.

  • 동시성:: task:: then 메서드를 사용 하 여 연속 작업을 만듭니다.Use the concurrency::task::then method to create continuations. 연속 은 다른 작업이 완료 된 후 비동기적으로 실행 되는 작업입니다.A continuation is a task that runs asynchronously after another task completes. 많은 연속을 연결하여 비동기 작업의 체인을 구성할 수 있습니다.You can connect any number of continuations to form a chain of asynchronous work.

  • 작업 기반 연속은 선행 작업이 취소되거나 예외를 throw하는 경우에도 선행 작업이 완료되면 항상 실행되도록 예약됩니다.A task-based continuation is always scheduled for execution when the antecedent task finishes, even when the antecedent task is canceled or throws an exception.

  • 작업 집합의 모든 멤버가 완료 된 후 완료 되는 작업을 만들려면 concurrency:: when_all 를 사용 합니다.Use concurrency::when_all to create a task that completes after every member of a set of tasks completes. 작업 집합의 한 멤버가 완료 된 후 완료 되는 작업을 만들려면 concurrency:: when_any 를 사용 합니다.Use concurrency::when_any to create a task that completes after one member of a set of tasks completes.

  • 작업 및 작업 그룹은 PPL(병렬 패턴 라이브러리) 취소 메커니즘에 참여할 수 있습니다.Tasks and task groups can participate in the Parallel Patterns Library (PPL) cancellation mechanism. 자세한 내용은 PPL에서의 취소를 참조 하세요.For more information, see Cancellation in the PPL.

  • 런타임에 작업 및 작업 그룹에서 throw 되는 예외를 처리 하는 방법에 대 한 자세한 내용은 예외 처리를 참조 하세요.To learn how the runtime handles exceptions that are thrown by tasks and task groups, see Exception Handling.

이 문서에서 다루는 내용In this Document

람다 식 사용Using Lambda Expressions

간결한 구문으로 인해 람다 식은 작업 및 작업 그룹에서 수행하는 작업을 정의하는 일반적인 방법입니다.Because of their succinct syntax, lambda expressions are a common way to define the work that is performed by tasks and task groups. 다음은 몇 가지 사용 팁입니다.Here are some usage tips:

  • 작업은 일반적으로 백그라운드 스레드에서 실행되므로 람다 식에서 변수를 캡처할 때 개체 수명을 알아 두어야 합니다.Because tasks typically run on background threads, be aware of the object lifetime when you capture variables in lambda expressions. 변수를 값으로 캡처하면 해당 변수의 복사본이 람다 본문에 생성됩니다.When you capture a variable by value, a copy of that variable is made in the lambda body. 참조로 캡처하면 복사본이 생성되지 않습니다.When you capture by reference, a copy is not made. 따라서 참조로 캡처하는 모든 변수의 수명은 해당 변수를 사용하는 작업보다 길어야 합니다.Therefore, ensure that the lifetime of any variable that you capture by reference outlives the task that uses it.

  • 작업에 람다 식을 전달 하는 경우 스택에 할당 된 변수를 참조로 캡처하지 마세요.When you pass a lambda expression to a task, don't capture variables that are allocated on the stack by reference.

  • 람다 식에서 캡처하는 변수를 명시적으로 지정 하 여 값을 기준으로 캡처하고 참조로 캡처할 항목을 식별할 수 있도록 합니다.Be explicit about the variables you capture in lambda expressions so that you can identify what you're capturing by value versus by reference. 이러한 이유로 람다 식에는 [=] 또는 [&] 옵션을 사용하지 않는 것이 좋습니다.For this reason we recommend that you do not use the [=] or [&] options for lambda expressions.

일반적인 패턴은 연속 체인의 한 작업이 변수에 할당되면 다른 작업에서 해당 변수를 읽는 것입니다.A common pattern is when one task in a continuation chain assigns to a variable, and another task reads that variable. 각 연속 작업은 다른 변수의 복사본을 보유 하기 때문에 값으로 캡처할 수 없습니다.You can't capture by value because each continuation task would hold a different copy of the variable. 스택 할당 변수의 경우 변수가 더 이상 유효 하지 않을 수 있으므로 참조로 캡처할 수 없습니다.For stack-allocated variables, you also can't capture by reference because the variable may no longer be valid.

이 문제를 해결 하려면 std:: shared_ptr와 같은 스마트 포인터를 사용 하 여 변수를 래핑하고 값을 사용 하 여 스마트 포인터를 전달 합니다.To solve this problem, use a smart pointer, such as std::shared_ptr, to wrap the variable and pass the smart pointer by value. 이런 식으로 기본 개체는 할당되고 읽을 수 있으며 해당 개체를 사용하는 작업보다 수명이 깁니다.In this way, the underlying object can be assigned to and read from, and will outlive the tasks that use it. 변수가 Windows 런타임 개체에 대한 포인터 또는 참조 개수가 계산되는 핸들(^)인 경우에도 이 기술을 사용합니다.Use this technique even when the variable is a pointer or a reference-counted handle (^) to a Windows Runtime object. 기본 예제는 다음과 같습니다.Here's a basic example:

// 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
*/

람다 식에 대 한 자세한 내용은 람다 식을 참조 하세요.For more information about lambda expressions, see Lambda Expressions.

작업 클래스The task Class

Concurrency:: task 클래스를 사용 하 여 작업을 종속 작업 집합으로 구성할 수 있습니다.You can use the concurrency::task class to compose tasks into a set of dependent operations. 이 컴퍼지션 모델은 연속의 개념에서 지원 됩니다.This composition model is supported by the notion of continuations. 연속을 사용 하면 이전 또는 선행작업이 완료 될 때 코드를 실행할 수 있습니다.A continuation enables code to be executed when the previous, or antecedent, task completes. 선행 작업의 결과는 하나 이상의 연속 작업에 대한 입력으로 전달됩니다.The result of the antecedent task is passed as the input to the one or more continuation tasks. 선행 작업이 완료되면 대기 중인 모든 연속 작업이 실행되도록 예약됩니다.When an antecedent task completes, any continuation tasks that are waiting on it are scheduled for execution. 각 연속 작업은 선행 작업 결과의 복사본을 받습니다.Each continuation task receives a copy of the result of the antecedent task. 그런 다음 해당 연속 작업은 다른 연속의 선행 작업이 되어 작업 체인을 만들 수도 있습니다.In turn, those continuation tasks may also be antecedent tasks for other continuations, thereby creating a chain of tasks. 연속에서는 작업 간에 특정 종속성이 있는 임의 길이의 작업 체인을 만들 수 있습니다.Continuations help you create arbitrary-length chains of tasks that have specific dependencies among them. 또한 작업은 작업이 시작되기 전에나 작업이 실행되는 동안 공동 작업 방식으로 취소에 참여할 수 있습니다.In addition, a task can participate in cancellation either before a tasks starts or in a cooperative manner while it is running. 이 취소 모델에 대 한 자세한 내용은 PPL에서의 취소를 참조 하세요.For more information about this cancellation model, see Cancellation in the PPL.

task는 템플릿 클래스입니다.task is a template class. 형식 매개 변수 T는 작업에 의해 생성되는 결과의 형식입니다.The type parameter T is the type of the result that is produced by the task. void 작업에서 값을 반환 하지 않는 경우이 형식은이 될 수 있습니다.This type can be void if the task does not return a value. T한정자를 사용할 수 없습니다 const .T cannot use the const modifier.

작업을 만들 때 작업 본문을 수행 하는 작업 함수 를 제공 합니다.When you create a task, you provide a work function that performs the task body. 이 작업 함수는 람다 함수, 함수 포인터 또는 함수 개체의 형태로 제공됩니다.This work function comes in the form of a lambda function, function pointer, or function object. 결과를 가져오지 않고 작업이 완료 될 때까지 기다리려면 concurrency:: task:: wait 메서드를 호출 합니다.To wait for a task to finish without obtaining the result, call the concurrency::task::wait method. task::wait메서드는 작업이 완료 또는 취소 되었는지 여부를 설명 하는 concurrency:: task_status 값을 반환 합니다.The task::wait method returns a concurrency::task_status value that describes whether the task was completed or canceled. 작업의 결과를 가져오려면 concurrency:: task:: get 메서드를 호출 합니다.To get the result of the task, call the concurrency::task::get method. 이 메서드는 task::wait를 호출하여 작업이 완료될 때까지 기다리므로 결과를 사용할 수 있을 때까지 현재 스레드의 실행을 차단합니다.This method calls task::wait to wait for the task to finish, and therefore blocks execution of the current thread until the result is available.

다음 예제에서는 작업을 만들고 결과를 기다린 후 해당 값을 표시하는 방법을 보여 줍니다.The following example shows how to create a task, wait for its result, and display its value. 이 설명서의 예제에서는 더 간결한 구문을 제공하는 람다 함수를 사용합니다.The examples in this documentation use lambda functions because they provide a more succinct syntax. 그러나 작업을 사용할 때는 함수 포인터 및 함수 개체를 사용할 수도 있습니다.However, you can also use function pointers and function objects when you use tasks.

// 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
*/

Concurrency:: create_task 함수를 사용 하는 경우 형식을 선언 하는 대신 키워드를 사용할 수 있습니다 auto .When you use the concurrency::create_task function, you can use the auto keyword instead of declaring the type. 예를 들어 항등 행렬을 만들고 인쇄하는 다음 코드를 살펴보겠습니다.For example, consider this code that creates and prints the identity matrix:

// 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 함수를 사용하여 동일한 작업을 만들 수 있습니다.You can use the create_task function to create the equivalent operation.

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;
});

작업을 실행하는 동안 예외가 throw되면 런타임은 task::get이나 task::wait 또는 작업 기반 연속에 대한 후속 호출에서 해당 예외를 마샬링합니다.If an exception is thrown during the execution of a task, the runtime marshals that exception in the subsequent call to task::get or task::wait, or to a task-based continuation. 작업 예외 처리 메커니즘에 대 한 자세한 내용은 예외 처리를 참조 하세요.For more information about the task exception-handling mechanism, see Exception Handling.

task, Concurrency:: task_completion_event, 취소를 사용 하는 예제는 연습: 작업 및 XML HTTP 요청을 사용 하 여 연결을 참조 하세요.For an example that uses task, concurrency::task_completion_event, cancellation, see Walkthrough: Connecting Using Tasks and XML HTTP Requests. task_completion_event 클래스는 이 문서의 뒷부분에 설명되어 있습니다.(The task_completion_event class is described later in this document.)

UWP 앱의 작업과 관련 된 자세한 내용은 c + +의 비동기 프로그래밍Uwp 앱 용 c + +로 비동기 작업 만들기를 참조 하세요.To learn details that are specific to tasks in UWP apps, see Asynchronous programming in C++ and Creating Asynchronous Operations in C++ for UWP Apps.

연속 작업Continuation Tasks

비동기 프로그래밍에서는 한 비동기 작업이 완료 시 두 번째 작업을 호출하고 해당 작업에 데이터를 전달하는 것이 일반적입니다.In asynchronous programming, it is very common for one asynchronous operation, on completion, to invoke a second operation and pass data to it. 일반적으로 이 작업은 콜백 메서드를 통해 수행됩니다.Traditionally, this is done by using callback methods. 동시성 런타임에서는 연속 작업을 통해 동일한 기능이 제공 됩니다.In the Concurrency Runtime, the same functionality is provided by continuation tasks. 연속 작업 (연속이 라고도 함)은 선행 작업이 완료 될 때 선행작업 이라고 하는 다른 작업에 의해 호출 되는 비동기 작업입니다.A continuation task (also known just as a continuation) is an asynchronous task that is invoked by another task, which is known as the antecedent, when the antecedent completes. 연속을 사용하여 다음을 수행할 수 있습니다.By using continuations, you can:

  • 선행 작업의 데이터를 연속 작업에 전달합니다.Pass data from the antecedent to the continuation.

  • 연속 작업이 호출되거나 호출되지 않는 정확한 조건을 지정합니다.Specify the precise conditions under which the continuation is invoked or not invoked.

  • 시작되기 전이나 실행 중일 때 함께 연속 작업을 취소합니다.Cancel a continuation either before it starts or cooperatively while it is running.

  • 연속 작업을 예약하는 방법에 대한 힌트를 제공합니다.Provide hints about how the continuation should be scheduled. 이는 UWP (유니버설 Windows 플랫폼) 앱에만 적용 됩니다.(This applies to Universal Windows Platform (UWP) apps only. 자세한 내용은 UWP 앱 용 c + +로 비동기 작업 만들기를 참조 하세요.For more information, see Creating Asynchronous Operations in C++ for UWP Apps.)

  • 동일한 선행 작업에서 여러 개의 연속 작업을 호출합니다.Invoke multiple continuations from the same antecedent.

  • 여러 선행 작업 중 하나 또는 모두가 완료되면 하나의 연속 작업을 호출합니다.Invoke one continuation when all or any of multiple antecedents complete.

  • 연속 작업을 임의 길이까지 차례로 연결합니다.Chain continuations one after another to any length.

  • 연속 작업을 사용하여 선행 작업에서 throw된 예외를 처리합니다.Use a continuation to handle exceptions that are thrown by the antecedent.

이러한 기능을 사용하여 첫 번째 작업이 완료될 때 하나 이상의 작업을 실행할 수 있습니다.These features enable you to execute one or more tasks when the first task completes. 예를 들어 첫 번째 작업에서 디스크로부터 파일을 읽은 후 해당 파일을 압축하는 연속 작업을 만들 수 있습니다.For example, you can create a continuation that compresses a file after the first task reads it from disk.

다음 예제에서는 concurrency:: task:: then 메서드를 사용 하 여 선행 작업의 값을 사용할 수 있는 경우이를 출력 하는 연속 작업을 예약 하는 이전 예제를 수정 합니다.The following example modifies the previous one to use the concurrency::task::then method to schedule a continuation that prints the value of the antecedent task when it is available.

// 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
*/

작업을 임의 길이까지 연결하고 중첩할 수 있습니다.You can chain and nest tasks to any length. 또한 작업에 여러 연속 작업을 포함할 수 있습니다.A task can also have multiple continuations. 다음 예제에서는 이전 작업의 값을 세 번 증가시키는 기본 연속 체인을 보여 줍니다.The following example illustrates a basic continuation chain that increments the value of the previous task three times.

// 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
*/

연속 작업은 다른 작업을 반환할 수도 있습니다.A continuation can also return another task. 취소가 없을 경우 이 작업은 후속 연속 전에 실행됩니다.If there is no cancellation, then this task is executed before the subsequent continuation. 이 기술을 비동기 래핑 해제합니다.This technique is known as asynchronous unwrapping. 비동기 래핑 해제는 백그라운드에서 추가 작업을 수행하려고 하지만 현재 작업에서 현재 스레드를 차단하지 않도록 하려는 경우에 유용합니다.Asynchronous unwrapping is useful when you want to perform additional work in the background, but do not want the current task to block the current thread. (이는 UWP 앱에서 발생 하며,이는 UI 스레드에서 연속 작업을 실행할 수 있는 경우에 일반적입니다.)(This is common in UWP apps, where continuations can run on the UI thread). 다음 예제에서는 세 가지 작업을 보여 줍니다.The following example shows three tasks. 첫 번째 작업에서는 연속 작업 전에 실행되는 다른 작업을 반환합니다.The first task returns another task that is run before a continuation task.

// 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 형식의 중첩된 작업을 반환하는 경우 결과 작업은 task<N>이 아니라 N 형식이며 중첩된 작업이 완료될 때 완료됩니다.When a continuation of a task returns a nested task of type N, the resulting task has the type N, not task<N>, and completes when the nested task completes. 즉, 연속에서 중첩된 작업의 래핑 해제를 수행합니다.In other words, the continuation performs the unwrapping of the nested task.

값 기반 연속 작업 및 작업 기반 연속Value-Based Versus Task-Based Continuations

반환 형식이 Ttask 개체를 지정하면 T 또는 task<T> 형식의 값을 연속 작업에 제공할 수 있습니다.Given a task object whose return type is T, you can provide a value of type T or task<T> to its continuation tasks. 형식을 사용 하는 연속 작업을 T 값 기반 연속이라고 합니다.A continuation that takes type T is known as a value-based continuation. 값 기반 연속은 선행 작업이 오류 없이 완료되고 취소되지 않으면 실행되도록 예약됩니다.A value-based continuation is scheduled for execution when the antecedent task completes without error and is not canceled. 형식을 매개 변수로 사용 하는 연속 task<T> 작업을 작업 기반 연속이라고 합니다.A continuation that takes type task<T> as its parameter is known as a task-based continuation. 작업 기반 연속은 선행 작업이 취소되거나 예외를 throw하는 경우에도 선행 작업이 완료되면 항상 실행되도록 예약됩니다.A task-based continuation is always scheduled for execution when the antecedent task finishes, even when the antecedent task is canceled or throws an exception. 그러면 task::get을 호출하여 선행 작업의 결과를 가져올 수 있습니다.You can then call task::get to get the result of the antecedent task. 선행 작업이 취소 된 경우에서 task::get concurrency:: task_canceled을 throw 합니다.If the antecedent task was canceled, task::get throws concurrency::task_canceled. 선행 작업에서 예외가 throw되면 task::get에서 해당 예외를 다시 throw합니다.If the antecedent task threw an exception, task::get rethrows that exception. 작업 기반 연속은 선행 작업이 취소되어도 취소됨으로 표시되지 않습니다.A task-based continuation is not marked as canceled when its antecedent task is canceled.

작업 작성Composing Tasks

이 단원에서는 여러 작업을 구성 하 여 공통 패턴을 구현 하는 데 도움이 되는 concurrency:: when_allconcurrency:: when_any 함수에 대해 설명 합니다.This section describes the concurrency::when_all and concurrency::when_any functions, which can help you compose multiple tasks to implement common patterns.

When_all 함수The when_all Function

when_all 함수는 작업 집합이 완료된 후 완료되는 작업을 생성합니다.The when_all function produces a task that completes after a set of tasks complete. 이 함수는 집합에 있는 각 작업의 결과를 포함 하는 std::vector 개체를 반환 합니다.This function returns a std::vector object that contains the result of each task in the set. 다음 기본 예제에서는 when_all을 사용하여 다른 세 작업의 완료를 나타내는 작업을 만듭니다.The following basic example uses when_all to create a task that represents the completion of three other tasks.

// 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에 전달되는 작업은 균일해야 합니다.The tasks that you pass to when_all must be uniform. 즉, 모두 동일한 형식을 반환해야 합니다.In other words, they must all return the same type.

다음 예제와 같이 && 구문을 사용하여 작업 집합이 완료된 후 완료되는 작업을 생성할 수도 있습니다.You can also use the && syntax to produce a task that completes after a set of tasks complete, as shown in the following example.

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

일반적으로 작업 집합이 완료된 후 작업을 수행하려면 when_all과 함께 연속을 사용합니다.It is common to use a continuation together with when_all to perform an action after a set of tasks finishes. 다음 예제에서는 이전 항목을 수정 하 여 각각 결과를 생성 하는 세 가지 작업의 합계를 인쇄 합니다 int .The following example modifies the previous one to print the sum of three tasks that each produce an int result.

// 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>>를 지정하여 작업 기반 연속을 생성할 수도 있습니다.In this example, you can also specify task<vector<int>> to produce a task-based continuation.

작업 집합의 모든 작업이 취소되거나 예외를 throw하는 경우 when_all은 즉시 완료되며 나머지 작업이 완료될 때까지 기다리지 않습니다.If any task in a set of tasks is canceled or throws an exception, when_all immediately completes and does not wait for the remaining tasks to finish. 예외가 throw되는 경우 런타임은 when_all이 반환하는 작업 개체에서 task::get 또는 task::wait를 호출할 때 예외를 다시 throw합니다.If an exception is thrown, the runtime rethrows the exception when you call task::get or task::wait on the task object that when_all returns. 둘 이상의 작업에서 예외를 throw하는 경우 런타임은 그중 하나를 선택합니다.If more than one task throws, the runtime chooses one of them. 따라서 모든 작업이 완료된 후 예외를 모두 관찰해야 합니다. 처리되지 않은 작업 예외로 인해 앱이 종료됩니다.Therefore, ensure that you observe all exceptions after all tasks complete; an unhandled task exception causes the app to terminate.

다음은 프로그램에서 모든 예외를 관찰 하는 데 사용할 수 있는 유틸리티 함수입니다.Here's a utility function that you can use to ensure that your program observes all exceptions. 제공된 범위의 각 작업에 대해 observe_all_exceptions는 발생된 예외를 다시 throw되도록 트리거한 다음 해당 예외를 swallow합니다.For each task in the provided range, observe_all_exceptions triggers any exception that occurred to be rethrown and then swallows that exception.

// 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 앱을 고려 합니다.Consider a UWP app that uses C++ and XAML and writes a set of files to disk. 다음 예제에서는 when_allobserve_all_exceptions를 사용하여 프로그램에서 모든 예외를 관찰하도록 하는 방법을 보여 줍니다.The following example shows how to use when_all and observe_all_exceptions to ensure that the program observes 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();
        }
    });
}
이 예제를 실행하려면To run this example
  1. MainPage.xaml에서 Button 컨트롤을 추가합니다.In MainPage.xaml, add a Button control.
<Button x:Name="Button1" Click="Button_Click">Write files</Button>
  1. MainPage .xaml에서 이러한 전방 선언을 private 클래스 선언의 섹션에 추가 합니다. MainPageIn MainPage.xaml.h, add these forward declarations to the private section of the MainPage class declaration.
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 이벤트 처리기를 구현합니다.In MainPage.xaml.cpp, implement the Button_Click event handler.
// 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를 구현합니다.In MainPage.xaml.cpp, implement WriteFilesAsync as shown in the example.

when_alltask를 해당 결과로 생성하는 비블로킹 함수입니다.when_all is a non-blocking function that produces a task as its result. Task:: wait와 달리 ASTA (응용 프로그램 STA) 스레드의 UWP 앱에서이 함수를 호출 하는 것이 안전 합니다.Unlike task::wait, it is safe to call this function in a UWP app on the ASTA (Application STA) thread.

When_any 함수The when_any Function

when_any 함수는 작업 집합의 첫 번째 작업이 완료되면 완료되는 작업을 생성합니다.The when_any function produces a task that completes when the first task in a set of tasks completes. 이 함수는 완료 된 작업의 결과와 집합에 있는 해당 태스크의 인덱스를 포함 하는 std::p air 개체를 반환 합니다.This function returns a std::pair object that contains the result of the completed task and the index of that task in the set.

when_any 함수는 다음과 같은 시나리오에서 특히 유용합니다.The when_any function is especially useful in the following scenarios:

  • 중복 작업입니다.Redundant operations. 알고리즘 또는 여러 방법으로 수행할 수 있는 작업을 고려합니다.Consider an algorithm or operation that can be performed in many ways. when_any 함수를 사용하여 먼저 완료되는 작업을 선택한 다음 나머지 작업을 취소할 수 있습니다.You can use the when_any function to select the operation that finishes first and then cancel the remaining operations.

  • 인터리브 작업입니다.Interleaved operations. 모두 완료되어야 하는 여러 작업을 시작하고 when_any 함수를 사용하여 각 작업이 완료되면 결과를 처리할 수 있습니다.You can start multiple operations that all must finish and use the when_any function to process results as each operation finishes. 하나의 작업이 완료되면 하나 이상의 추가 작업을 시작할 수 있습니다.After one operation finishes, you can start one or more additional tasks.

  • 제한된 작업입니다.Throttled operations. when_any 함수를 사용하여 동시 작업 수를 제한하여 이전 시나리오를 확장할 수 있습니다.You can use the when_any function to extend the previous scenario by limiting the number of concurrent operations.

  • 만료된 작업입니다.Expired operations. when_any 함수를 사용하여 하나 이상의 작업과 특정 시간 이후에 완료되는 작업 중에서 선택할 수 있습니다.You can use the when_any function to select between one or more tasks and a task that finishes after a specific time.

when_all과 마찬가지로 when_any가 포함된 연속을 사용하여 작업 집합의 첫 번째 작업이 완료되면 작업을 수행할 수 있습니다.As with when_all, it is common to use a continuation that has when_any to perform action when the first in a set of tasks finish. 다음 기본 예제에서는 when_any를 사용하여 다른 세 작업 중 첫 번째 작업이 완료되면 완료되는 작업을 만듭니다.The following basic example uses when_any to create a task that completes when the first of three other tasks completes.

// 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>>를 지정하여 작업 기반 연속을 생성할 수도 있습니다.In this example, you can also specify task<pair<int, size_t>> to produce a task-based continuation.

참고

when_all과 마찬가지로 when_any에 전달되는 작업은 모두 동일한 형식을 반환해야 합니다.As with when_all, the tasks that you pass to when_any must all return the same type.

다음 예제와 같이 || 구문을 사용하여 작업 집합의 첫 번째 작업이 완료된 후 완료되는 작업을 생성할 수도 있습니다.You can also use the || syntax to produce a task that completes after the first task in a set of tasks completes, as shown in the following example.

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

와 마찬가지로 when_all when_any 는 차단 되지 않으며 ASTA 스레드의 UWP 앱에서를 호출 하는 것이 안전 합니다.As with when_all, when_any is non-blocking and is safe to call in a UWP app on the ASTA thread.

지연 된 작업 실행Delayed Task Execution

경우에 따라 조건이 충족될 때까지 작업 실행을 지연하거나 외부 이벤트에 대한 응답으로 작업을 시작해야 할 수 있습니다.It is sometimes necessary to delay the execution of a task until a condition is satisfied, or to start a task in response to an external event. 예를 들어 비동기 프로그래밍에서 I/O 완료 이벤트에 대한 응답으로 작업을 시작해야 할 수 있습니다.For example, in asynchronous programming, you might have to start a task in response to an I/O completion event.

이 작업을 수행 하는 두 가지 방법은 연속 작업을 사용 하거나 작업을 시작 하 고 작업의 작업 함수 내에서 이벤트를 대기 하는 것입니다.Two ways to accomplish this are to use a continuation or to start a task and wait on an event inside the task's work function. 그러나 이러한 기술을 하나를 사용할 수 없는 경우가 있습니다.However, there are cases where is it not possible to use one of these techniques. 예를 들어 연속을 만들려면 선행 작업이 있어야 합니다.For example, to create a continuation, you must have the antecedent task. 그러나 선행 작업이 없는 경우 작업 완료 이벤트 를 만들고 나중에 사용할 수 있게 되 면 선행 작업에 해당 완료 이벤트를 연결할 수 있습니다.However, if you do not have the antecedent task, you can create a task completion event and later chain that completion event to the antecedent task when it becomes available. 또한 대기 중인 작업은 스레드도 차단하므로 작업 완료 이벤트를 사용하여 비동기 작업이 완료되면 작업을 수행함으로써 스레드를 해제할 수 있습니다.In addition, because a waiting task also blocks a thread, you can use task completion events to perform work when an asynchronous operation completes, and thereby free a thread.

Concurrency:: task_completion_event 클래스는 이러한 작업 컴퍼지션을 간소화 하는 데 도움이 됩니다.The concurrency::task_completion_event class helps simplify such composition of tasks. task 클래스처럼 형식 매개 변수 T는 작업에 의해 생성되는 결과의 형식입니다.Like the task class, the type parameter T is the type of the result that is produced by the task. void 작업에서 값을 반환 하지 않는 경우이 형식은이 될 수 있습니다.This type can be void if the task does not return a value. T한정자를 사용할 수 없습니다 const .T cannot use the const modifier. 일반적으로 task_completion_event 개체는 값을 사용할 수 있을 때 신호를 보내는 작업이나 스레드에 제공됩니다.Typically, a task_completion_event object is provided to a thread or task that will signal it when the value for it becomes available. 동시에 하나 이상의 작업이 해당 이벤트의 수신기로 설정됩니다.At the same time, one or more tasks are set as listeners of that event. 이벤트가 설정되면 수신기 작업이 완료되고 작업의 연속 실행이 예약됩니다.When the event is set, the listener tasks complete and their continuations are scheduled to run.

를 사용 하 여 task_completion_event 지연 후 완료 되는 작업을 구현 하는 예제는 방법: 지연 후 완료 되는 작업 만들기를 참조 하세요.For an example that uses task_completion_event to implement a task that completes after a delay, see How to: Create a Task that Completes After a Delay.

작업 그룹Task Groups

작업 그룹 은 작업 컬렉션을 구성 합니다.A task group organizes a collection of tasks. 작업 그룹은 작업 가로채기 큐에 작업을 넣습니다.Task groups push tasks on to a work-stealing queue. 스케줄러는 이 큐에서 작업을 제거하고 사용 가능한 컴퓨팅 리소스에서 실행합니다.The scheduler removes tasks from this queue and executes them on available computing resources. 작업 그룹에 작업을 추가한 후 모든 작업이 완료되거나 아직 시작되지 않은 작업이 취소될 때까지 기다릴 수 있습니다.After you add tasks to a task group, you can wait for all tasks to finish or cancel tasks that have not yet started.

PPL은 concurrency:: task_groupconcurrency:: structured_task_group 클래스를 사용 하 여 작업 그룹을 나타내고 concurrency:: task_handle 클래스를 사용 하 여 이러한 그룹에서 실행 되는 작업을 나타냅니다.The PPL uses the concurrency::task_group and concurrency::structured_task_group classes to represent task groups, and the concurrency::task_handle class to represent the tasks that run in these groups. task_handle 클래스는 작업을 수행하는 코드를 캡슐화합니다.The task_handle class encapsulates the code that performs work. task 클래스와 마찬가지로 작업 함수는 람다 함수, 함수 포인터 또는 함수 개체의 형태로 제공됩니다.Like the task class, the work function comes in the form of a lambda function, function pointer, or function object. 일반적으로 task_handle 개체에서 직접 작업할 필요가 없습니다.You typically do not need to work with task_handle objects directly. 대신 작업 그룹에 작업 함수를 전달하면 작업 그룹에서 task_handle 개체를 만들고 관리합니다.Instead, you pass work functions to a task group, and the task group creates and manages the task_handle objects.

PPL은 작업 그룹을 비구조적 작업 그룹과 구조적 작업 그룹이라는 두 가지 범주로 나눕니다.The PPL divides task groups into these two categories: unstructured task groups and structured task groups. PPL은 task_group 클래스를 사용하여 비구조적 작업 그룹을 나타내고 structured_task_group 클래스를 사용하여 구조적 작업 그룹을 나타냅니다.The PPL uses the task_group class to represent unstructured task groups and the structured_task_group class to represent structured task groups.

중요

또한 PPL은 클래스를 사용 하 여 작업 집합을 병렬로 실행 하는 동시성::p arallel_invoke 알고리즘을 정의 합니다 structured_task_group .The PPL also defines the concurrency::parallel_invoke algorithm, which uses the structured_task_group class to execute a set of tasks in parallel. parallel_invoke 알고리즘에는 더 간결한 구문이 있으므로 가능한 경우 structured_task_group 클래스 대신 사용하는 것이 좋습니다.Because the parallel_invoke algorithm has a more succinct syntax, we recommend that you use it instead of the structured_task_group class when you can. 항목 병렬 알고리즘parallel_invoke 보다 자세히 설명 합니다.The topic Parallel Algorithms describes parallel_invoke in greater detail.

동시에 실행하려는 여러 독립된 작업이 있고 계속하려면 모든 작업이 완료될 때까지 기다려야 하는 경우 parallel_invoke를 사용합니다.Use parallel_invoke when you have several independent tasks that you want to execute at the same time, and you must wait for all tasks to finish before you continue. 이 기법은 종종 포크 및 조인 병렬 처리 라고 합니다.This technique is often referred to as fork and join parallelism. 동시에 실행하려는 여러 독립된 작업이 있지만 작업이 나중에 완료될 때까지 기다리려면 task_group을 사용합니다.Use task_group when you have several independent tasks that you want to execute at the same time, but you want to wait for the tasks to finish at a later time. 예를 들어 task_group 개체에 작업을 추가하고 작업이 다른 함수 또는 다른 스레드에서 완료될 때까지 기다릴 수 있습니다.For example, you can add tasks to a task_group object and wait for the tasks to finish in another function or from another thread.

작업 그룹은 취소 개념을 지원합니다.Task groups support the concept of cancellation. 취소를 사용하면 전체 작업을 취소하려고 한다는 사실을 모든 활성 작업에 알릴 수 있습니다.Cancellation enables you to signal to all active tasks that you want to cancel the overall operation. 또한 취소를 사용하면 아직 시작되지 않은 작업이 시작되지 않도록 방지할 수 있습니다.Cancellation also prevents tasks that have not yet started from starting. 취소에 대 한 자세한 내용은 PPL에서의 취소를 참조 하세요.For more information about cancellation, see Cancellation in the PPL.

또한 런타임은 작업에서 예외를 throw하고 연결된 작업 그룹이 완료될 때까지 기다린 후 해당 예외를 처리할 수 있도록 하는 예외 처리 모델을 제공합니다.The runtime also provides an exception-handling model that enables you to throw an exception from a task and handle that exception when you wait for the associated task group to finish. 이 예외 처리 모델에 대 한 자세한 내용은 예외 처리를 참조 하세요.For more information about this exception-handling model, see Exception Handling.

Structured_task_group task_group 비교Comparing task_group to structured_task_group

structured_task_group 클래스 대신 task_group 또는 parallel_invoke를 사용하는 것이 좋지만 다양한 작업을 수행하거나 취소를 지원해야 하는 병렬 알고리즘을 작성하는 경우처럼 structured_task_group을 사용해야 하는 경우가 있습니다.Although we recommend that you use task_group or parallel_invoke instead of the structured_task_group class, there are cases where you want to use structured_task_group, for example, when you write a parallel algorithm that performs a variable number of tasks or requires support for cancellation. 이 섹션에서는 task_groupstructured_task_group의 차이점에 대해 설명합니다.This section explains the differences between the task_group and structured_task_group classes.

task_group 클래스는 스레드로부터 안전합니다.The task_group class is thread-safe. 따라서 여러 스레드에서 task_group 개체에 작업을 추가하고 여러 스레드에서 task_group 개체를 대기하거나 취소할 수 있습니다.Therefore you can add tasks to a task_group object from multiple threads and wait on or cancel a task_group object from multiple threads. structured_task_group 개체의 생성 및 소멸은 동일한 어휘 범위에서 발생해야 합니다.The construction and destruction of a structured_task_group object must occur in the same lexical scope. 또한 structured_task_group 개체의 모든 작업은 동일한 스레드에서 발생해야 합니다.In addition, all operations on a structured_task_group object must occur on the same thread. 이 규칙의 예외는 concurrency:: structured_task_group:: cancelconcurrency:: structured_task_group:: is_canceling 메서드입니다.The exception to this rule is the concurrency::structured_task_group::cancel and concurrency::structured_task_group::is_canceling methods. 자식 작업은 언제든지 이러한 메서드를 호출하여 부모 작업 그룹을 취소하거나 취소를 확인할 수 있습니다.A child task can call these methods to cancel the parent task group or check for cancelation at any time.

task_group Concurrency:: task_group:: wait 또는 concurrency:: task_group:: run_and_wait 메서드를 호출한 후 개체에서 추가 작업을 실행할 수 있습니다.You can run additional tasks on a task_group object after you call the concurrency::task_group::wait or concurrency::task_group::run_and_wait method. 반대로 structured_task_group concurrency:: structured_task_group:: wait 또는 concurrency:: structured_task_group:: run_and_wait 메서드를 호출한 후 개체에서 추가 작업을 실행 하는 경우 동작이 정의 되지 않습니다.Conversely, if you run additional tasks on a structured_task_group object after you call the concurrency::structured_task_group::wait or concurrency::structured_task_group::run_and_wait methods, then the behavior is undefined.

structured_task_group 클래스는 스레드에서 동기화되지 않으므로 task_group 클래스보다 실행 오버헤드가 적습니다.Because the structured_task_group class does not synchronize across threads, it has less execution overhead than the task_group class. 따라서 문제가 여러 스레드에서 작업을 예약하도록 요구하지 않고 parallel_invoke 알고리즘을 사용할 수 없는 경우 structured_task_group 클래스를 사용하여 더 잘 수행되는 코드를 작성할 수 있습니다.Therefore, if your problem does not require that you schedule work from multiple threads and you cannot use the parallel_invoke algorithm, the structured_task_group class can help you write better performing code.

다른 structured_task_group 개체 내에서 structured_task_group 을 사용하는 경우 외부 개체가 완료되기 전에 내부 개체를 완료하고 삭제해야 합니다.If you use one structured_task_group object inside another structured_task_group object, the inner object must finish and be destroyed before the outer object finishes. task_group 클래스에서는 외부 그룹이 완료되기 전에 중첩된 작업 그룹이 완료될 필요가 없습니다.The task_group class does not require for nested task groups to finish before the outer group finishes.

비구조적 작업 그룹과 구조적 작업 그룹은 다양한 방법으로 작업 핸들에서 작동합니다.Unstructured task groups and structured task groups work with task handles in different ways. 작업 함수를 task_group 개체에 직접 전달할 수 있으며, task_group 개체에서 작업 핸들을 만들고 관리합니다.You can pass work functions directly to a task_group object; the task_group object will create and manage the task handle for you. structured_task_group 클래스에서는 각 작업에 대해 task_handle 개체를 관리해야 합니다.The structured_task_group class requires you to manage a task_handle object for each task. 모든 task_handle 개체는 연결된 structured_task_group 개체의 수명 동안 내내 유효한 상태로 유지되어야 합니다.Every task_handle object must remain valid throughout the lifetime of its associated structured_task_group object. 다음 기본 예제와 같이 concurrency:: make_task 함수를 사용 하 여 개체를 만듭니다 task_handle .Use the concurrency::make_task function to create a task_handle object, as shown in the following basic example:

// 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와 같은 컨테이너 클래스를 사용 합니다.To manage task handles for cases where you have a variable number of tasks, use a stack-allocation routine such as _malloca or a container class, such as std::vector.

task_groupstructured_task_group 둘 다 취소를 지원합니다.Both task_group and structured_task_group support cancellation. 취소에 대 한 자세한 내용은 PPL에서의 취소를 참조 하세요.For more information about cancellation, see Cancellation in the PPL.

예제Example

다음 기본 예제에서는 작업 그룹을 사용하는 방법을 보여 줍니다.The following basic example shows how to work with task groups. 이 예제에서는 parallel_invoke 알고리즘을 사용하여 두 작업을 동시에 수행합니다.This example uses the parallel_invoke algorithm to perform two tasks concurrently. 각 작업은 하위 작업을 task_group 개체에 추가합니다.Each task adds sub-tasks to a task_group object. task_group 클래스를 사용하면 여러 작업이 동시에 이 클래스에 작업을 추가할 수 있습니다.Note that the task_group class allows for multiple tasks to add tasks to it concurrently.

// 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();
}

다음은 이 예제의 샘플 출력입니다.The following is sample output for this example:

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

parallel_invoke 알고리즘은 작업을 동시에 실행하므로 출력 메시지의 순서가 다를 수 있습니다.Because the parallel_invoke algorithm runs tasks concurrently, the order of the output messages could vary.

알고리즘을 사용 하는 방법을 보여 주는 전체 예제 parallel_invoke 는 방법: Parallel_invoke를 사용 하 여 병렬 정렬 루틴 작성방법: parallel_invoke를 사용 하 여 병렬 작업 실행을 참조 하세요.For complete examples that show how to use the parallel_invoke algorithm, see How to: Use parallel_invoke to Write a Parallel Sort Routine and How to: Use parallel_invoke to Execute Parallel Operations. 클래스를 사용 하 여 task_group 비동기 미래를 구현 하는 전체 예제는 연습: 미래 구현을 참조 하세요.For a complete example that uses the task_group class to implement asynchronous futures, see Walkthrough: Implementing Futures.

강력한 프로그래밍Robust Programming

작업, 작업 그룹 및 병렬 알고리즘을 사용하는 경우 취소 및 예외 처리의 역할을 이해하고 있어야 합니다.Make sure that you understand the role of cancellation and exception handling when you use tasks, task groups, and parallel algorithms. 예를 들어 병렬 작업 트리에서 취소되는 작업은 자식 작업이 실행되지 않도록 방지합니다.For example, in a tree of parallel work, a task that is canceled prevents child tasks from running. 따라서 자식 작업 중 하나가 리소스 해제와 같이 애플리케이션에 중요한 작업을 수행하는 경우 문제가 발생할 수 있습니다.This can cause problems if one of the child tasks performs an operation that is important to your application, such as freeing a resource. 또한 자식 작업에서 예외를 throw하는 경우 해당 예외가 개체 소멸자를 통해 전파되어 애플리케이션에서 정의되지 않은 동작이 발생할 수 있습니다.In addition, if a child task throws an exception, that exception could propagate through an object destructor and cause undefined behavior in your application. 이러한 점을 보여 주는 예제는 병렬 패턴 라이브러리 문서에서 모범 사례의 취소 및 예외 처리가 개체 소멸에 미치는 영향 이해 섹션을 참조 하세요.For an example that illustrates these points, see the Understand how Cancellation and Exception Handling Affect Object Destruction section in the Best Practices in the Parallel Patterns Library document. PPL의 취소 및 예외 처리 모델에 대 한 자세한 내용은 취소예외 처리를 참조 하세요.For more information about the cancellation and exception-handling models in the PPL, see Cancellation and Exception Handling.

제목Title 설명Description
방법: parallel_invoke를 사용 하 여 병렬 정렬 루틴 작성How to: Use parallel_invoke to Write a Parallel Sort Routine parallel_invoke 알고리즘을 사용하여 바이토닉 정렬 알고리즘의 성능을 향상시키는 방법을 보여 줍니다.Shows how to use the parallel_invoke algorithm to improve the performance of the bitonic sort algorithm.
방법: parallel_invoke를 사용 하 여 병렬 작업 실행How to: Use parallel_invoke to Execute Parallel Operations parallel_invoke 알고리즘을 사용하여 공유 데이터 소스에서 여러 작업을 수행하는 프로그램의 성능을 향상시키는 방법을 보여 줍니다.Shows how to use the parallel_invoke algorithm to improve the performance of a program that performs multiple operations on a shared data source.
방법: 지연 후 완료 되는 작업 만들기How to: Create a Task that Completes After a Delay ,, 및 클래스를 사용 하 여 task cancellation_token_source cancellation_token task_completion_event 지연 후 완료 되는 작업을 만드는 방법을 보여 줍니다.Shows how to use the task, cancellation_token_source, cancellation_token, and task_completion_event classes to create a task that completes after a delay.
연습: 미래 구현Walkthrough: Implementing Futures 동시성 런타임의 기존 기능을 더 많은 역할을 하는 기능과 결합하는 방법을 보여 줍니다.Shows how to combine existing functionality in the Concurrency Runtime into something that does more.
PPL(병렬 패턴 라이브러리)Parallel Patterns Library (PPL) 동시 애플리케이션 개발을 위해 명령적 프로그래밍 모델을 제공하는 PPL에 대해 설명합니다.Describes the PPL, which provides an imperative programming model for developing concurrent applications.

참조Reference

작업 클래스 (동시성 런타임)task Class (Concurrency Runtime)

task_completion_event 클래스task_completion_event Class

when_all 함수when_all Function

when_any 함수when_any Function

task_group 클래스task_group Class

parallel_invoke 함수parallel_invoke Function

structured_task_group 클래스structured_task_group Class