UWP 앱용 C++ 비동기 작업 만들기

이 문서에서는 작업 클래스를 사용하여 UWP(유니버설 Windows 런타임) 앱에서 Windows ThreadPool 기반 비동기 작업을 생성할 때 유의해야 할 몇 가지 주요 사항을 설명합니다.

비동기 프로그래밍의 사용은 앱이 사용자 입력에 응답할 기본 있도록 하기 때문에 Windows 런타임 앱 모델의 핵심 구성 요소입니다. UI 스레드를 차단하지 않고 장기 실행 작업을 시작할 수 있으며 해당 작업의 결과를 나중에 받을 수 있습니다. 또한 작업을 취소하고 작업이 백그라운드에서 실행될 때 진행률 알림을 받을 수도 있습니다. C++의 문서 비동기 프로그래밍은 Visual C++에서 UWP 앱을 만드는 데 사용할 수 있는 비동기 패턴에 대한 개요를 제공합니다. 이 문서에서는 비동기 Windows 런타임 작업의 체인을 사용하고 만드는 방법을 설명합니다. 이 섹션에서는 ppltasks.h의 형식을 사용하여 다른 Windows 런타임 구성 요소에서 사용할 수 있는 비동기 작업을 생성하는 방법과 비동기 작업이 실행되는 방법을 제어하는 방법을 설명합니다. 또한 Hilo에서 비동기 프로그래밍 패턴 및 팁(C++ 및 XAML을 사용하는 Windows 스토어 앱)을 읽고 작업 클래스를 사용하여 C++ 및 XAML을 사용하는 Windows 런타임 앱인 Hilo에서 비동기 작업을 구현하는 방법을 알아보세요.

참고 항목

UWP 앱에서 PPL(병렬 패턴 라이브러리 ) 및 비동기 에이전트 라이브러리 를 사용할 수 있습니다. 그러나 작업 스케줄러 또는 리소스 관리자는 사용할 수 없습니다. 이 문서에서는 PPL이 데스크톱 앱이 아닌 UWP 앱에서만 사용할 수 있는 추가 기능을 설명합니다.

핵심 내용

  • concurrency::create_async 를 사용하여 다른 구성 요소(C++ 이외의 언어로 작성될 수도 있음)에서 사용할 수 있는 비동기 작업을 만듭니다.

  • concurrency::progress_reporter 를 사용하여 비동기 작업을 호출하는 구성 요소에 진행률 알림을 보고합니다.

  • 취소 토큰을 사용하여 내부 비동기 작업이 취소될 수 있도록 합니다.

  • create_async 함수의 동작은 이 함수에 전달된 작업 함수의 반환 형식에 따라 달라집니다. 작업( task<T> 또는 task<void>)을 반환하는 작업 함수는 create_async를 호출한 컨텍스트에서 동기적으로 실행됩니다. T 또는 void 를 반환하는 작업 함수는 임의의 컨텍스트에서 실행됩니다.

  • concurrency::task::then 메서드를 사용하여 차례로 실행되는 작업의 체인을 만들 수 있습니다. UWP 앱에서 작업의 연속 작업에 대한 기본 컨텍스트는 해당 작업이 생성된 방법에 따라 달라집니다. 비동기 동작을 작업 생성자에 전달하거나 비동기 동작을 반환하는 람다 식을 전달하여 작업을 만든 경우 해당 작업의 모든 연속에 대한 기본 컨텍스트는 현재 컨텍스트입니다. 작업이 비동기 작업에서 생성되지 않은 경우 임의 컨텍스트는 기본적으로 작업의 연속 작업에 사용됩니다. 기본 컨텍스트는 concurrency::task_continuation_context 클래스를 사용하여 재정의할 수 있습니다.

이 문서의 내용

비동기 작업 만들기

PPL(병렬 패턴 라이브러리)에서 작업 및 연속 모델을 사용하여 백그라운드 작업과 이전 작업이 완료될 때 실행되는 추가 작업을 정의할 수 있습니다. 이 기능은 concurrency:: task 클래스를 통해 제공됩니다. 이 모델과 task 클래스에 대한 자세한 내용은 Task Parallelism를 호출한 컨텍스트에서 동기적으로 실행됩니다.

Windows 런타임 특수 운영 체제 환경에서만 실행되는 UWP 앱을 만드는 데 사용할 수 있는 프로그래밍 인터페이스입니다. 이러한 앱은 인증된 기능, 데이터 형식 및 디바이스를 사용하고 Microsoft Store에서 배포됩니다. Windows 런타임 ABI(애플리케이션 이진 인터페이스)로 표시됩니다. ABI는 Visual C++와 같은 프로그래밍 언어에서 Windows 런타임 API를 사용할 수 있도록 하는 기본 이진 계약입니다.

Windows 런타임 사용하여 다양한 프로그래밍 언어의 최상의 기능을 사용하고 하나의 앱으로 결합할 수 있습니다. 예를 들어 JavaScript에서 UI를 만들고 C++ 구성 요소에서 계산이 많은 앱 논리를 수행할 수 있습니다. 이러한 계산이 많은 작업을 백그라운드에서 수행하는 기능은 UI 응답 성능을 유지하는 데 핵심적인 요소입니다. task 클래스는 C++와 관련이 있으므로 Windows 런타임 인터페이스를 사용하여 비동기 작업을 다른 구성 요소(C++가 아닌 언어로 작성될 수 있습니다)와 통신해야 합니다. Windows 런타임 비동기 작업을 나타내는 데 사용할 수 있는 네 가지 인터페이스를 제공합니다.

Windows::Foundation::IAsyncAction
비동기 동작을 나타냅니다.

Windows::Foundation::IAsyncActionWithProgress<TProgress>
진행률을 보고하는 비동기 동작을 나타냅니다.

Windows::Foundation::IAsyncOperation<TResult>
결과를 반환하는 비동기 작업을 나타냅니다.

Windows::Foundation::IAsyncOperationWithProgress<TResult, TProgress>
결과를 반환하고 진행률을 보고하는 비동기 작업을 나타냅니다.

여기서 동작(action) 은 비동기 작업에서 값을 생성하지 않음을 의미하고( void를 반환하는 함수와 유사함), 작업(operation) 은 비동기 작업에서 값을 생성함을 의미합니다. 진행률(progress) 은 작업에서 진행률 메시지를 호출자에게 보고할 수 있음을 의미합니다. JavaScript, .NET Framework 및 Visual C++에서는 ABI 경계를 넘어 사용할 수 있도록 이러한 인터페이스의 인스턴스를 만드는 방법을 자체적으로 제공합니다. Visual C++의 경우 PPL에서 concurrency:: create_async 함수를 제공합니다. 이 함수는 작업의 완료를 나타내는 Windows 런타임 비동기 작업 또는 작업을 만듭니다. 함수는 create_async 작업 함수(일반적으로 람다 식)를 사용하고, 내부적으로 개체를 task 만들고, 해당 작업을 4개의 비동기 Windows 런타임 인터페이스 중 하나로 래핑합니다.

참고 항목

다른 언어 또는 다른 Windows 런타임 구성 요소에서 액세스할 수 있는 기능을 만들어야 하는 경우에만 사용합니다create_async. 작업이 동일한 구성 요소에서 C++ 코드를 통해 생성되고 사용되는 것을 알고 있는 경우에는 task 클래스를 직접 사용합니다.

create_async 의 반환 형식은 해당 인수의 형식에 따라 결정됩니다. 예를 들어 작업 함수에서 값을 반환하지 않고 진행률을 보고하지 않는 경우 create_asyncIAsyncAction을 반환합니다. 작업 함수에서 값을 반환하지 않고 진행률을 보고하는 경우 create_asyncIAsyncActionWithProgress를 반환합니다. 진행률을 보고하려면 concurrency::progress_reporter 개체를 작업 함수에 매개 변수로 제공합니다. 진행률을 보고하는 기능을 사용하면 수행된 작업량과 남아 있는 작업량을 백분율 등으로 보고할 수 있습니다. 또한 결과가 사용 가능해지면 결과도 보고할 수 있습니다.

IAsyncAction, IAsyncActionWithProgress<TProgress>, IAsyncOperation<TResult>IAsyncActionOperationWithProgress<TProgress, TProgress> 인터페이스는 비동기 작업을 취소하는 데 사용할 수 있는 Cancel 메서드를 각기 제공합니다. task 클래스는 취소 토큰과 함께 작동합니다. 취소 토큰을 사용하여 작업을 취소하면 런타임에서 해당 토큰을 구독하는 새 작업을 시작하지 않습니다. 이미 활성화된 작업은 취소 토큰을 모니터링하고 가능한 경우 중지할 수 있습니다. 이 메커니즘에 대해서는 Cancellation in the PPL문서에 자세히 설명되어 있습니다. 두 가지 방법으로 작업 취소를 Windows 런타임 Cancel 메서드와 연결할 수 있습니다. 첫째, create_async concurrency::cancellation_token 개체를 사용하도록 에 전달하는 작업 함수를 정의할 수 있습니다. 메서드가 Cancel 호출되면 이 취소 토큰이 취소되고 일반 취소 규칙이 호출을 task 지원하는 기본 개체에 create_async 적용됩니다. cancellation_token 개체를 제공하지 않는 경우 내부 task 개체는 암시적으로 이 개체를 정의합니다. 작업 함수에서 취소에 협조적으로 응답해야 하는 경우에는 cancellation_token 개체를 정의해야 합니다. 섹션 예제: C++ 및 XAML을 사용하여 Windows 런타임 앱에서 실행 제어는 사용자 지정 Windows 런타임 C++ 구성 요소를 사용하는 C# 및 XAML을 사용하여 UWP(유니버설 Windows 플랫폼) 앱에서 취소를 수행하는 방법의 예를 보여 줍니다.

Warning

작업 연속의 체인에서 항상 상태를 클린 취소 토큰이 취소되면 동시성::cancel_current_task을 호출합니다. cancel_current_task를 호출하지 않고 조기에 반환하는 경우 작업이 취소된 상태가 아니라 완료된 상태로 전환됩니다.

다음 표에는 앱에서 비동기 작업을 정의하는 데 사용할 수 있는 조합이 요약되어 있습니다.

이 Windows 런타임 인터페이스를 만들려면 create_async 암시적 취소 토큰을 사용하기 위해 작업 함수에 전달할 매개 변수 형식 명시적 취소 토큰을 사용하기 위해 작업 함수에 전달할 매개 변수 형식
IAsyncAction void 또는 task<void> (없음) (cancellation_token)
IAsyncActionWithProgress<TProgress> void 또는 task<void> (progress_reporter) (progress_reporter, cancellation_token)
IAsyncOperation<TResult> T 또는 task<T> (없음) (cancellation_token)
IAsyncActionOperationWithProgress<TProgress, TProgress> T 또는 task<T> (progress_reporter) (progress_reporter, cancellation_token)

task 함수에 전달하는 작업 함수에서 create_async 개체를 반환하거나 값을 반환할 수 있습니다. 이러한 변형은 각기 다른 동작을 생성합니다. 값을 반환하는 경우 작업 함수는 백그라운드 스레드에서 실행될 수 있도록 task 에 래핑됩니다. 또한 내부 task 는 암시적 취소 토큰을 사용합니다. 반대로 task 개체를 반환하는 경우 작업 함수는 동기적으로 실행됩니다. 따라서 task 개체를 반환하는 경우에는 앱이 응답 성능을 유지할 수 있도록 작업 함수에서 시간이 많이 걸리는 모든 작업이 작업으로 실행되어야 합니다. 또한 내부 task 는 암시적 취소 토큰을 사용하지 않습니다. 따라서 cancellation_token 에서 task 개체를 반환할 때 취소에 대한 지원이 필요한 경우 create_async개체를 사용하도록 작업 함수를 정의해야 합니다.

다음 예제에서는 다른 Windows 런타임 구성 요소에서 사용할 수 있는 개체를 IAsyncAction 만드는 다양한 방법을 보여줍니다.

// Creates an IAsyncAction object and uses an implicit cancellation token.
auto op1 = create_async([]
{
    // Define work here.
});

// Creates an IAsyncAction object and uses no cancellation token.
auto op2 = create_async([]
{
    return create_task([]
    {
        // Define work here.
    });
});

// Creates an IAsyncAction object and uses an explicit cancellation token.
auto op3 = create_async([](cancellation_token ct)
{
    // Define work here.
});

// Creates an IAsyncAction object that runs another task and also uses an explicit cancellation token.
auto op4 = create_async([](cancellation_token ct)
{
    return create_task([ct]()
    {
        // Define work here.
    });
});

예: C++ Windows 런타임 구성 요소 만들기 및 C에서 사용#

XAML 및 C#을 사용하여 컴퓨팅 집약적 작업을 수행하기 위해 UI 및 C++ Windows 런타임 구성 요소를 정의하는 앱을 고려합니다. 이 예에서 C++ 구성 요소는 지정된 범위에서 소수인 수를 계산합니다. 4개의 Windows 런타임 비동기 작업 인터페이스 간의 차이점을 설명하기 위해 빈 솔루션을 만들고 이름을 지정하여 Visual Studio에서 시작합니다Primes. 그런 다음 솔루션에 Windows 런타임 구성 요소 프로젝트를 추가하고 이름을 PrimesLibrary로 지정합니다. 생성된 C++ 헤더 파일에 다음 코드를 추가합니다(이 예에서는 Class1.h의 이름을 Primes.h로 변경). 각 public 메서드는 네 가지 비동기 인터페이스 중 하나를 정의합니다. 값을 반환하는 메서드는 Windows::Foundation::Collections::IVector int> 개체를 반환합니다.< 진행률을 보고하는 메서드는 완료된 전체 작업의 백분율을 정의하는 double 값을 생성합니다.

#pragma once

namespace PrimesLibrary
{
    public ref class Primes sealed
    {
    public:
        Primes();

        // Computes the numbers that are prime in the provided range and stores them in an internal variable.
        Windows::Foundation::IAsyncAction^ ComputePrimesAsync(int first, int last);

        // Computes the numbers that are prime in the provided range and stores them in an internal variable.
        // This version also reports progress messages.
        Windows::Foundation::IAsyncActionWithProgress<double>^ ComputePrimesWithProgressAsync(int first, int last);

        // Gets the numbers that are prime in the provided range.
        Windows::Foundation::IAsyncOperation<Windows::Foundation::Collections::IVector<int>^>^ GetPrimesAsync(int first, int last);

        // Gets the numbers that are prime in the provided range. This version also reports progress messages.
        Windows::Foundation::IAsyncOperationWithProgress<Windows::Foundation::Collections::IVector<int>^, double>^ GetPrimesWithProgressAsync(int first, int last);
    };
}

참고 항목

규칙에 따라 Windows 런타임 비동기 메서드 이름은 일반적으로 "Async"로 끝납니다.

생성된 C++ 소스 파일에 다음 코드를 추가합니다(이 예에서는 Class1.cpp의 이름을 Primes.cpp로 변경). is_prime 함수는 입력이 소수인지 여부를 확인합니다. 나머지 메서드는 Primes 클래스를 구현합니다. create_async 를 호출할 때마다 이 함수가 호출된 메서드와 호환되는 서명이 사용됩니다. 예를 들어 Primes::ComputePrimesAsyncIAsyncAction을 반환하기 때문에 create_async 에 제공되는 작업 함수는 값을 반환하지 않으며 progress_reporter 개체를 매개 변수로 사용하지 않습니다.

// PrimesLibrary.cpp
#include "pch.h"
#include "Primes.h"
#include <atomic>
#include <collection.h>
#include <ppltasks.h>
#include <concurrent_vector.h>

using namespace concurrency;
using namespace std;

using namespace Platform;
using namespace Platform::Collections;
using namespace Windows::Foundation;
using namespace Windows::Foundation::Collections;

using namespace PrimesLibrary;

Primes::Primes()
{
}

// Determines whether the input value is prime. 
bool is_prime(int n)
{
    if (n < 2)
    {
        return false;
    }
    for (int i = 2; i < n; ++i)
    {
        if ((n % i) == 0)
        {
            return false;
        }
    }
    return true;
}

// Adds the numbers that are prime in the provided range  
// to the primes global variable.
IAsyncAction^ Primes::ComputePrimesAsync(int first, int last)
{
    return create_async([this, first, last]
    {
        // Ensure that the input values are in range. 
        if (first < 0 || last < 0)
        {
            throw ref new InvalidArgumentException();
        }
        // Perform the computation in parallel.
        parallel_for(first, last + 1, [this](int n)
        {
            if (is_prime(n))
            {
                // Perhaps store the value somewhere...
            }
        });
    });
}

IAsyncActionWithProgress<double>^ Primes::ComputePrimesWithProgressAsync(int first, int last)
{
    return create_async([first, last](progress_reporter<double> reporter)
    {
        // Ensure that the input values are in range.
        if (first < 0 || last < 0)
        {
            throw ref new InvalidArgumentException();
        }
        // Perform the computation in parallel. 
        atomic<long> operation = 0;
        long range = last - first + 1;
        double lastPercent = 0.0;
        parallel_for(first, last + 1, [&operation, range, &lastPercent, reporter](int n)
        {
            // Report progress message.
            double progress = 100.0 * (++operation) / range;
            if (progress >= lastPercent)
            {
                reporter.report(progress);
                lastPercent += 1.0;
            }

            if (is_prime(n))
            {
                // Perhaps store the value somewhere...
            }
        });
        reporter.report(100.0);
    });
}

IAsyncOperation<IVector<int>^>^ Primes::GetPrimesAsync(int first, int last)
{
    return create_async([this, first, last]() -> IVector<int>^
    {
        // Ensure that the input values are in range. 
        if (first < 0 || last < 0)
        {
            throw ref new InvalidArgumentException();
        }
        // Perform the computation in parallel.
        concurrent_vector<int> primes;
        parallel_for(first, last + 1, [this, &primes](int n)
        {
            // If the value is prime, add it to the global vector.
            if (is_prime(n))
            {
                primes.push_back(n);
            }
        });
        // Sort the results.
        sort(begin(primes), end(primes), less<int>());

        // Copy the results to an IVector object. The IVector 
        // interface makes collections of data available to other 
        // Windows Runtime components.
        auto results = ref new Vector<int>();
        for (int prime : primes)
        {
            results->Append(prime);
        }
        return results;
    });
}

IAsyncOperationWithProgress<IVector<int>^, double>^ Primes::GetPrimesWithProgressAsync(int first, int last)
{
    return create_async([this, first, last](progress_reporter<double> reporter) -> IVector<int>^
    {
        // Ensure that the input values are in range.
        if (first < 0 || last < 0)
        {
            throw ref new InvalidArgumentException();
        }
        // Perform the computation in parallel.
        concurrent_vector<int> primes;
        long operation = 0;
        long range = last - first + 1;
        double lastPercent = 0.0;
        parallel_for(first, last + 1, [&primes, &operation, range, &lastPercent, reporter](int n)
        {
            // Report progress message.
            double progress = 100.0 * (++operation) / range;
            if (progress >= lastPercent)
            {
                reporter.report(progress);
                lastPercent += 1.0;
            }

            // If the value is prime, add it to the local vector. 
            if (is_prime(n))
            {
                primes.push_back(n);
            }
        });
        reporter.report(100.0);

        // Sort the results.
        sort(begin(primes), end(primes), less<int>());

        // Copy the results to an IVector object. The IVector 
        // interface makes collections of data available to other 
        // Windows Runtime components.
        auto results = ref new Vector<int>();
        for (int prime : primes)
        {
            results->Append(prime);
        }
        return results;
    });
}

각 메서드는 먼저 유효성 검사를 수행하여 입력 매개 변수가 음수가 아닌지 확인합니다. 입력 값이 음수이면 메서드에서 Platform::InvalidArgumentException을 throw합니다. 오류 처리에 대해서는 이 섹션의 뒷부분에서 설명합니다.

UWP 앱에서 이러한 메서드를 사용하려면 Visual C# 빈 앱(XAML) 템플릿을 사용하여 Visual Studio 솔루션에 두 번째 프로젝트를 추가합니다. 이 예에서는 프로젝트 이름을 Primes로 지정합니다. 그런 다음 Primes 프로젝트에서 PrimesLibrary 프로젝트에 대한 참조를 추가합니다.

MainPage.xaml에 다음 코드를 추가합니다. 이 코드에서는 C++ 구성 요소를 호출하고 결과를 표시할 수 있도록 UI를 정의합니다.

<Page
    x:Class="Primes.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:Primes"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="300"/>
            <ColumnDefinition Width="300"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="125"/>
            <RowDefinition Height="125"/>
            <RowDefinition Height="125"/>
        </Grid.RowDefinitions>

        <StackPanel Grid.Column="0" Grid.Row="0">
            <Button Name="b1" Click="computePrimes">Compute Primes</Button>
            <TextBlock Name="tb1"></TextBlock>
        </StackPanel>

        <StackPanel Grid.Column="1" Grid.Row="0">
            <Button Name="b2" Click="computePrimesWithProgress">Compute Primes with Progress</Button>
            <ProgressBar Name="pb1" HorizontalAlignment="Left" Width="100"></ProgressBar>
            <TextBlock Name="tb2"></TextBlock>
        </StackPanel>

        <StackPanel Grid.Column="0" Grid.Row="1">
            <Button Name="b3" Click="getPrimes">Get Primes</Button>
            <TextBlock Name="tb3"></TextBlock>
        </StackPanel>

        <StackPanel Grid.Column="1" Grid.Row="1">
            <Button Name="b4" Click="getPrimesWithProgress">Get Primes with Progress</Button>
            <ProgressBar Name="pb4"  HorizontalAlignment="Left" Width="100"></ProgressBar>
            <TextBlock Name="tb4"></TextBlock>
        </StackPanel>

        <StackPanel Grid.Column="0" Grid.Row="2">
            <Button Name="b5" Click="getPrimesHandleErrors">Get Primes and Handle Errors</Button>
            <ProgressBar Name="pb5"  HorizontalAlignment="Left" Width="100"></ProgressBar>
            <TextBlock Name="tb5"></TextBlock>
        </StackPanel>

        <StackPanel Grid.Column="1" Grid.Row="2">
            <Button Name="b6" Click="getPrimesCancellation">Get Primes with Cancellation</Button>
            <Button Name="cancelButton" Click="cancelGetPrimes" IsEnabled="false">Cancel</Button>
            <ProgressBar Name="pb6"  HorizontalAlignment="Left" Width="100"></ProgressBar>
            <TextBlock Name="tb6"></TextBlock>
        </StackPanel>
    </Grid>
</Page>

MainPage.xaml의 MainPage 클래스에 다음 코드를 추가합니다. 이 코드에서는 Primes 개체와 단추 이벤트 처리기를 정의합니다.

private PrimesLibrary.Primes primesLib = new PrimesLibrary.Primes();

private async void computePrimes(object sender, RoutedEventArgs e)
{
    b1.IsEnabled = false;
    tb1.Text = "Working...";

    var asyncAction = primesLib.ComputePrimesAsync(0, 100000);

    await asyncAction;

    tb1.Text = "Done";
    b1.IsEnabled = true;
}

private async void computePrimesWithProgress(object sender, RoutedEventArgs e)
{
    b2.IsEnabled = false;
    tb2.Text = "Working...";

    var asyncAction = primesLib.ComputePrimesWithProgressAsync(0, 100000);
    asyncAction.Progress = new AsyncActionProgressHandler<double>((action, progress) =>
    {
        pb1.Value = progress;
    });

    await asyncAction;

    tb2.Text = "Done";
    b2.IsEnabled = true;
}

private async void getPrimes(object sender, RoutedEventArgs e)
{
    b3.IsEnabled = false;
    tb3.Text = "Working...";

    var asyncOperation = primesLib.GetPrimesAsync(0, 100000);

    await asyncOperation;

    tb3.Text = "Found " + asyncOperation.GetResults().Count + " primes";
    b3.IsEnabled = true;
}

private async void getPrimesWithProgress(object sender, RoutedEventArgs e)
{
    b4.IsEnabled = false;
    tb4.Text = "Working...";

    var asyncOperation = primesLib.GetPrimesWithProgressAsync(0, 100000);
    asyncOperation.Progress = new AsyncOperationProgressHandler<IList<int>, double>((operation, progress) =>
    {
        pb4.Value = progress;
    });

    await asyncOperation;

    tb4.Text = "Found " + asyncOperation.GetResults().Count + " primes";
    b4.IsEnabled = true;
}

private async void getPrimesHandleErrors(object sender, RoutedEventArgs e)
{
    b5.IsEnabled = false;
    tb5.Text = "Working...";

    var asyncOperation = primesLib.GetPrimesWithProgressAsync(-1000, 100000);
    asyncOperation.Progress = new AsyncOperationProgressHandler<IList<int>, double>((operation, progress) =>
    {
        pb5.Value = progress;
    });

    try
    {
        await asyncOperation;
        tb5.Text = "Found " + asyncOperation.GetResults().Count + " primes";
    }
    catch (ArgumentException ex)
    {
        tb5.Text = "ERROR: " + ex.Message;
    }

    b5.IsEnabled = true;
}

private IAsyncOperationWithProgress<IList<int>, double> asyncCancelableOperation;

private async void getPrimesCancellation(object sender, RoutedEventArgs e)
{
    b6.IsEnabled = false;
    cancelButton.IsEnabled = true;
    tb6.Text = "Working...";

    asyncCancelableOperation = primesLib.GetPrimesWithProgressAsync(0, 200000);
    asyncCancelableOperation.Progress = new AsyncOperationProgressHandler<IList<int>, double>((operation, progress) =>
    {
        pb6.Value = progress;
    });

    try
    {
        await asyncCancelableOperation;
        tb6.Text = "Found " + asyncCancelableOperation.GetResults().Count + " primes";
    }
    catch (System.Threading.Tasks.TaskCanceledException)
    {
        tb6.Text = "Operation canceled";
    }

    b6.IsEnabled = true;
    cancelButton.IsEnabled = false;
}

private void cancelGetPrimes(object sender, RoutedEventArgs e)
{
    cancelButton.IsEnabled = false;
    asyncCancelableOperation.Cancel();
}

이러한 메서드는 asyncawait 키워드를 사용하여 비동기 작업이 완료된 후 UI를 업데이트합니다. UWP 앱의 비동기 코딩에 대한 자세한 내용은 스레딩 및 비동기 프로그래밍을 참조 하세요.

getPrimesCancellationcancelGetPrimes 메서드는 함께 작동하여 사용자가 작업을 취소할 수 있도록 합니다. 사용자가 취소 단추를 선택하면 메서드는 IAsyncOperationWithProgress<TResult, TProgress>::Cancel을 호출하여 작업을 취소 cancelGetPrimes 합니다. 기본 비동기 작업을 관리하는 동시성 런타임은 Windows 런타임 의해 catch된 내부 예외 유형을 throw하여 취소가 완료되었음을 전달합니다. 취소 모델에 대한 자세한 내용은 취소를 참조하세요.

Important

PPL이 작업을 취소한 Windows 런타임 올바르게 보고할 수 있도록 하려면 이 내부 예외 유형을 catch하지 마세요. 즉, 모든 예외도 catch하지 않아야 합니다(catch (...)). 모든 예외를 catch해야 하는 경우 예외를 다시 throw하여 Windows 런타임 취소 작업을 완료할 수 있는지 확인합니다.

다음 그림에서는 각 옵션을 선택한 후의 Primes 앱을 보여 줍니다.

Windows Runtime Primes app.

다른 언어에서 사용할 수 있는 비동기 작업을 만드는 데 사용하는 create_async 예제는 Bing 지도 Trip Optimizer 샘플에서 C++ 사용을 참조하세요.

실행 스레드 제어

Windows 런타임 COM 스레딩 모델을 사용합니다. 이 모델에서 개체는 동기화를 처리하는 방식에 따라 서로 다른 아파트에 호스트됩니다. 스레드로부터 안전한 개체는 MTA(다중 스레드 아파트)에서 호스트됩니다. 단일 스레드에서 액세스해야 하는 개체는 STA(단일 스레드 아파트)에서 호스트됩니다.

UI가 있는 앱에서 ASTA(애플리케이션 STA) 스레드는 창 메시지를 펌프하는 작업을 담당하며 STA에서 호스트된 UI 컨트롤을 업데이트할 수 있는 프로세스의 유일한 스레드입니다. 이에 따라 두 가지 결과가 발생합니다. 첫째, 앱의 응답 성능을 유지하기 위해 CPU를 많이 사용하는 모든 I/O 작업은 ASTA 스레드에서 실행되지 않아야 합니다. 둘째, 백그라운드 스레드에서 제공되는 결과는 UI를 업데이트하기 위해 ASTA에 다시 마샬링되어야 합니다. C++ UWP 앱 MainPage 및 기타 XAML 페이지는 모두 ATSA에서 실행됩니다. 따라서 ASTA에서 선언된 작업 연속은 기본적으로 그곳에서 실행되므로 연속 본문에서 직접 컨트롤을 업데이트할 수 있습니다. 그러나 작업을 다른 작업에 중첩하는 경우 중첩된 작업의 모든 연속은 MTA에서 실행됩니다. 따라서 이러한 연속이 실행되는 컨텍스트를 명시적으로 지정할지 여부를 고려해야 합니다.

IAsyncOperation<TResult>과 같은 비동기 작업에서 만든 작업은 스레딩 세부 사항을 무시하는 데 도움이 될 수 있는 특수한 의미 체계를 사용합니다. 작업이 백그라운드 스레드에서 실행되거나 스레드를 통해 전혀 지원되지 않을 수 있지만 작업의 연속은 연속 작업을 시작한 아파트(즉, task::then을 호출한 아파트)에서 실행되도록 기본적으로 보장됩니다. concurrency::task_continuation_context 클래스를 사용하여 연속의 실행 컨텍스트를 제어할 수 있습니다. task_continuation_context 개체를 만들려면 다음과 같은 정적 도우미 메서드를 사용합니다.

task_continuation_context 개체를 task:: then 메서드에 전달하여 연속의 실행 컨텍스트를 명시적으로 제어하거나, 작업을 다른 아파트에 전달한 다음 task::then 메서드를 호출하여 실행 컨텍스트를 암시적으로 제어할 수 있습니다.

Important

UWP 앱의 기본 UI 스레드는 STA에서 실행되므로 기본적으로 해당 STA에서 만드는 연속은 STA에서 실행됩니다. 따라서 MTA에서 만드는 연속은 MTA에서 실행됩니다.

다음 섹션에서는 디스크에서 파일을 읽고 해당 파일에서 가장 일반적인 단어를 찾은 다음 UI에 결과를 표시하는 앱을 보여 줍니다. UI를 업데이트하는 최종 작업은 UI 스레드에서 발생합니다.

Important

이 동작은 UWP 앱과 관련이 있습니다. 데스크톱 앱의 경우 연속이 실행되는 위치를 제어하지 않습니다. 대신 스케줄러가 각 연속을 실행할 작업자 스레드를 선택합니다.

Important

STA에서 실행되는 연속의 본문에서 concurrency::task::wait 를 호출하지 마세요. 호출하는 경우 이 메서드가 현재 스레드를 차단하고 앱이 응답하지 않게 만들 수 있기 때문에 런타임에서 concurrency::invalid_operation 을 throw합니다. 그러나 concurrency::task::get 메서드를 호출하여 작업 기반 연속에서 선행 작업의 결과를 받을 수 있습니다.

예: C++ 및 XAML을 사용하여 Windows 런타임 앱에서 실행 제어

디스크에서 파일을 읽고 해당 파일에서 가장 일반적인 단어를 찾은 다음 UI에 결과를 표시하는 C++ XAML 앱을 살펴보겠습니다. 이 앱을 만들려면 빈 앱(유니버설 Windows) 프로젝트를 만들고 이름을 지정하여 Visual Studio에서 시작합니다CommonWords. 앱 매니페스트에서 문서 라이브러리 접근 권한을 지정하여 앱이 문서 폴더에 액세스할 수 있도록 합니다. 또한 텍스트(.txt) 파일 형식을 앱 매니페스트의 선언 섹션에 추가합니다. 앱 기능 및 선언에 대한 자세한 내용은 Windows 앱의 패키징, 배포 및 쿼리를 참조 하세요.

MainPage.xaml에서 Grid 요소를 업데이트하여 ProgressRing 요소와 TextBlock 요소를 포함합니다. ProgressRing 은 작업이 진행 중임을 나타내고, TextBlock 은 계산의 결과를 보여 줍니다.

<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
    <ProgressRing x:Name="Progress"/>
    <TextBlock x:Name="Results" FontSize="16"/>
</Grid>

pch.h다음 #include 문을 추가합니다.

#include <sstream>
#include <ppltasks.h>
#include <concurrent_unordered_map.h>

MainPage 클래스에 다음 메서드 선언을 추가합니다(MainPage.h).

private:
    // Splits the provided text string into individual words.
    concurrency::task<std::vector<std::wstring>> MakeWordList(Platform::String^ text);

    // Finds the most common words that are at least the provided minimum length.
    concurrency::task<std::vector<std::pair<std::wstring, size_t>>> FindCommonWords(const std::vector<std::wstring>& words, size_t min_length, size_t count);

    // Shows the most common words on the UI.
    void ShowResults(const std::vector<std::pair<std::wstring, size_t>>& commonWords);

MainPage.cpp에 다음 using 문을 추가합니다.

using namespace concurrency;
using namespace std;
using namespace Windows::Storage;
using namespace Windows::Storage::Streams;

MainPage.cpp에서 MainPage::MakeWordList, MainPage::FindCommonWordsMainPage::ShowResults 메서드를 구현합니다. MainPage::MakeWordListMainPage::FindCommonWords 는 계산이 많은 작업을 수행합니다. MainPage::ShowResults 메서드는 UI에 계산의 결과를 표시합니다.

// Splits the provided text string into individual words.
task<vector<wstring>> MainPage::MakeWordList(String^ text)
{
    return create_task([text]() -> vector<wstring>
    {
        vector<wstring> words;

        // Add continuous sequences of alphanumeric characters to the string vector.
        wstring current_word;
        for (wchar_t ch : text)
        {
            if (!iswalnum(ch))
            {
                if (current_word.length() > 0)
                {
                    words.push_back(current_word);
                    current_word.clear();
                }
            }
            else
            {
                current_word += ch;
            }
        }

        return words;
    });
}

// Finds the most common words that are at least the provided minimum length.
task<vector<pair<wstring, size_t>>> MainPage::FindCommonWords(const vector<wstring>& words, size_t min_length, size_t count)
{
    return create_task([words, min_length, count]() -> vector<pair<wstring, size_t>>
    {
        typedef pair<wstring, size_t> pair;

        // Counts the occurrences of each word.
        concurrent_unordered_map<wstring, size_t> counts;

        parallel_for_each(begin(words), end(words), [&counts, min_length](const wstring& word)
        {
            // Increment the count of words that are at least the minimum length. 
            if (word.length() >= min_length)
            {
                // Increment the count.
                InterlockedIncrement(&counts[word]);
            }
        });

        // Copy the contents of the map to a vector and sort the vector by the number of occurrences of each word.
        vector<pair> wordvector;
        copy(begin(counts), end(counts), back_inserter(wordvector));

        sort(begin(wordvector), end(wordvector), [](const pair& x, const pair& y)
        {
            return x.second > y.second;
        });

        size_t size = min(wordvector.size(), count);
        wordvector.erase(begin(wordvector) + size, end(wordvector));

        return wordvector;
    });
}

// Shows the most common words on the UI. 
void MainPage::ShowResults(const vector<pair<wstring, size_t>>& commonWords)
{
    wstringstream ss;
    ss << "The most common words that have five or more letters are:";
    for (auto commonWord : commonWords)
    {
        ss << endl << commonWord.first << L" (" << commonWord.second << L')';
    }

    // Update the UI.
    Results->Text = ref new String(ss.str().c_str());
}

MainPage 생성자를 수정하여 Homer의 The Iliad 라는 책에 있는 일반적인 단어를 UI에 표시하는 연속 작업의 체인을 만듭니다. 텍스트를 개별 단어로 분할하고 일반적인 단어를 찾는 처음 두 연속 작업은 시간이 걸릴 수 있으므로 백그라운드에서 실행되도록 명시적으로 설정됩니다. UI를 업데이트하는 최종 연속 작업은 연속 컨텍스트를 지정하지 않으므로 아파트 스레딩 규칙을 따릅니다.

MainPage::MainPage()
{
    InitializeComponent();

    // To run this example, save the contents of http://www.gutenberg.org/files/6130/6130-0.txt to your Documents folder.
    // Name the file "The Iliad.txt" and save it under UTF-8 encoding.

    // Enable the progress ring.
    Progress->IsActive = true;

    // Find the most common words in the book "The Iliad".

    // Get the file.
    create_task(KnownFolders::DocumentsLibrary->GetFileAsync("The Iliad.txt")).then([](StorageFile^ file)
    {
        // Read the file text.
        return FileIO::ReadTextAsync(file, UnicodeEncoding::Utf8);

        // By default, all continuations from a Windows Runtime async operation run on the 
        // thread that calls task.then. Specify use_arbitrary to run this continuation 
        // on a background thread.
    }, task_continuation_context::use_arbitrary()).then([this](String^ file)
    {
        // Create a word list from the text.
        return MakeWordList(file);

        // By default, all continuations from a Windows Runtime async operation run on the 
        // thread that calls task.then. Specify use_arbitrary to run this continuation 
        // on a background thread.
    }, task_continuation_context::use_arbitrary()).then([this](vector<wstring> words)
    {
        // Find the most common words.
        return FindCommonWords(words, 5, 9);

        // By default, all continuations from a Windows Runtime async operation run on the 
        // thread that calls task.then. Specify use_arbitrary to run this continuation 
        // on a background thread.
    }, task_continuation_context::use_arbitrary()).then([this](vector<pair<wstring, size_t>> commonWords)
    {
        // Stop the progress ring.
        Progress->IsActive = false;

        // Show the results.
        ShowResults(commonWords);

        // We don't specify a continuation context here because we want the continuation 
        // to run on the STA thread.
    });
}

참고 항목

이 예에서는 실행 컨텍스트를 지정하는 방법과 연속의 체인을 구성하는 방법을 보여 줍니다. 앞에서 설명했듯이 기본적으로 비동기 작업에서 만든 작업은 task::then을 호출한 아파트에서 연속을 실행합니다. 따라서 이 예에서는 task_continuation_context::use_arbitrary 를 사용하여 UI와 관련이 없는 작업이 백그라운드 스레드에서 수행되도록 지정합니다.

다음 그림에서는 CommonWords 앱의 결과를 보여 줍니다.

Windows Runtime CommonWords app.

이 예제에서는 지원하는 개체가 암시적 취소 토큰을 task 사용하므로 취소를 지원할 create_async 수 있습니다. 작업이 협조적으로 취소에 응답해야 하는 경우 cancellation_token 개체를 사용하도록 작업 함수를 정의합니다. PPL에서의 취소에 대한 자세한 내용은 Cancellation in the PPL을 참조하세요.

참고 항목

동시성 런타임