이 문서에서는 작업 클래스를 사용하여 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 앱에서만 사용할 수 있는 추가 기능을 설명합니다.
핵심 사항
동시성::create_async 사용하여 다른 구성 요소에서 사용할 수 있는 비동기 작업을 만듭니다(C++가 아닌 언어로 작성될 수 있습니다).
concurrency::progress_reporter를 사용하여 비동기 작업을 호출하는 구성 요소에 진행률 알림을 보고해야 합니다.
취소 토큰을 사용하여 내부 비동기 작업을 취소할 수 있습니다.
함수의
create_async
동작은 함수에 전달되는 작업 함수의 반환 형식에 따라 달라집니다. 작업 함수는task<T>
또는task<void>
작업을 반환하며,create_async
를 호출한 컨텍스트에서 동기적으로 실행됩니다. 임의의 컨텍스트에서T
을(를) 반환하거나void
을(를) 실행하는 작업 함수입니다.동시성::task::then 메서드를 사용하여 하나씩 실행되는 작업 체인을 만들 수 있습니다. UWP 앱에서 작업의 연속 작업에 대한 기본 컨텍스트는 해당 작업이 생성된 방법에 따라 달라집니다. 작업 생성자에 비동기 작업을 전달하거나 비동기 작업을 반환하는 람다 식을 전달하여 작업을 만든 경우 해당 작업의 모든 연속 작업에 대한 기본 컨텍스트는 현재 컨텍스트입니다. 작업이 비동기 작업에서 생성되지 않은 경우 임의 컨텍스트는 기본적으로 작업의 연속 작업에 사용됩니다. 동시성::task_continuation_context 클래스를 사용하여 기본 컨텍스트를 재정의할 수 있습니다.
이 문서에서는
비동기 작업 만들기
PPL(병렬 패턴 라이브러리)의 작업 및 연속 모델을 사용하여 백그라운드 작업과 이전 작업이 완료된 경우 실행되는 추가 작업을 정의할 수 있습니다. 이 기능은 동시성::task 클래스에서 제공됩니다. 이 모델 및 task
클래스에 대한 자세한 내용은 작업 병렬 처리를 참조하세요.
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>
결과를 반환하고 진행률을 보고하는 비동기 작업을 나타냅니다.
동작의 개념은 비동기 작업이 값을 생성하지 않음을 의미합니다(반환void
되는 함수를 생각).
작업의 개념은 비동기 작업이 값을 생성한다는 것을 의미합니다.
진행률 개념은 태스크가 진행률 메시지를 호출자에게 보고할 수 있음을 의미합니다. JavaScript, .NET Framework 및 Visual C++는 각각 ABI 경계에서 사용할 이러한 인터페이스의 인스턴스를 만드는 고유한 방법을 제공합니다. Visual C++의 경우 PPL은 동시성::create_async 함수를 제공합니다. 이 함수는 작업 완료를 나타내는 Windows 런타임 비동기 작업 또는 작업을 만듭니다. 함수는 create_async
작업 함수(일반적으로 람다 식)를 사용하고, 내부적으로 개체를 task
만들고, 4개의 비동기 Windows 런타임 인터페이스 중 하나에서 해당 작업을 래핑합니다.
비고
다른 언어 또는 다른 Windows 런타임 구성 요소에서 액세스할 수 있는 기능을 만들어야 하는 경우에만 사용합니다 create_async
. 작업이 동일한 구성 요소의 task
C++ 코드에서 생성되고 사용된다는 것을 알고 있는 경우 클래스를 직접 사용합니다.
반환 형식 create_async
은 인수의 형식에 따라 결정됩니다. 예를 들어, 작업 함수가 값을 반환하지 않고 진행률을 보고하지 않으면 create_async
는 IAsyncAction
을 반환합니다. 작업 함수가 값을 반환하지 않고 진행률을 보고하면 create_async
가 반환되어 IAsyncActionWithProgress
가 됩니다. 진행 상황을 보고하려면 동시성::progress_reporter 객체를 작업 함수의 매개 변수로 제공합니다. 진행률을 보고할 수 있으므로 수행된 작업량과 남은 작업량(예: 백분율)을 보고할 수 있습니다. 결과가 준비되는 대로 보고할 수 있습니다.
IAsyncAction
, IAsyncActionWithProgress<TProgress>
, IAsyncOperation<TResult>
, 및 IAsyncActionOperationWithProgress<TProgress, TProgress>
인터페이스 각각은 비동기 작업을 취소할 수 있는 Cancel
메서드를 제공합니다. 클래스는 task
취소 토큰과 함께 작동합니다. 취소 토큰을 사용하여 작업을 취소하면 런타임에서 해당 토큰을 구독하는 새 작업을 시작하지 않습니다. 이미 활성 상태인 작업은 취소 토큰을 모니터링하고 가능하면 중지할 수 있습니다. 이 메커니즘은 PPL에서의 취소라는 문서에서 자세히 설명되어 있습니다. 작업 취소는 두 가지 방법으로 Windows 런타임 Cancel
메서드와 연결할 수 있습니다. 처음으로, create_async
객체를 받아 에 전달할 작업 함수를 정의할 수 있습니다. 메서드가 Cancel
호출될 때 이 취소 토큰이 취소되고, 일반 취소 규칙이 task
호출을 지원하는 기본 개체에 적용됩니다. 개체를 cancellation_token
제공하지 않으면 기본 task
개체가 암시적으로 개체를 정의합니다. 작업 함수에서 취소를 협력적으로 처리해야 할 때 cancellation_token
개체를 정의하십시오.
섹션 예제: C++ 및 XAML을 사용하여 Windows 런타임 앱에서 실행을 제어하는 방법은 사용자 지정 Windows 런타임 C++ 구성 요소를 사용하는 C# 및 XAML을 사용하여 UWP(유니버설 Windows 플랫폼) 앱에서 취소를 수행하는 방법의 예를 보여 줍니다.
경고
작업의 연속 체인에서, 항상 상태를 정리한 후, 취소 토큰이 취소될 때 동시성::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
개체를 반환할 때 취소 지원이 필요하면 cancellation_token
작업 함수가 cancellation_token
개체를 받을 수 있도록 정의해야 합니다.
다음 예제에서는 다른 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++ 소스 파일에 다음 코드를 추가합니다(이 예제에서는 Primes.cpp Class1.cpp 이름을 바꿉니다). 함수는 is_prime
해당 입력이 소수인지 여부를 결정합니다. 나머지 메서드는 클래스를 구현합니다 Primes
. 각 호출은 create_async
호출되는 메서드와 호환되는 서명을 사용합니다. 예를 들어, Primes::ComputePrimesAsync
가 IAsyncAction
을 반환하기 때문에, 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();
}
이러한 메서드는 비동기 작업이 완료된 후 async
키워드와 await
키워드를 사용하여 UI를 업데이트합니다. UWP 앱의 비동기 코딩에 대한 자세한 내용은 스레딩 및 비동기 프로그래밍을 참조하세요.
getPrimesCancellation
및 cancelGetPrimes
메서드는 사용자가 작업을 취소할 수 있도록 함께 작동합니다. 사용자가 취소 단추를 cancelGetPrimes
선택하면 메서드는 IAsyncOperationWithProgress<TResult, TProgress>::Cancel 을 호출하여 작업을 취소합니다. 기본 비동기 작업을 관리하는 동시성 런타임은 Windows 런타임에서 catch한 내부 예외 유형을 throw하여 취소가 완료되었음을 전달합니다. 취소 모델에 대한 자세한 내용은 취소를 참조 하세요.
중요합니다
PPL이 작업을 취소했음을 Windows 런타임에 올바르게 보고할 수 있도록 하려면 이 내부 예외 유형을 catch하지 마십시오. 즉, 모든 예외(catch (...)
)도 catch해서는 안 됩니다. 모든 예외를 catch해야 하는 경우, 예외를 다시 발생시켜 Windows 런타임에서 취소 작업을 완료하도록 하십시오.
다음 그림은 각 옵션을 선택한 후의 앱을 보여줍니다.
다른 언어에서 사용할 수 있는 비동기 작업을 만드는 데 사용하는 create_async
예제는 Bing Maps Trip Optimizer 샘플에서 C++ 사용을 참조하세요.
실행 스레드 제어
Windows 런타임은 COM 스레딩 모델을 사용합니다. 이 모델에서 개체는 동기화를 처리하는 방법에 따라 다른 아파트에서 호스트됩니다. 스레드로부터 안전한 개체는 MTA(다중 스레드 아파트)에서 호스트됩니다. 단일 스레드에서 액세스해야 하는 개체는 STA(단일 스레드 아파트)에서 호스트됩니다.
UI가 있는 앱에서 ASTA(Application STA) 스레드는 창 메시지 펌핑을 담당하며 STA 호스팅 UI 컨트롤을 업데이트할 수 있는 프로세스의 유일한 스레드입니다. 여기에는 두 가지 결과가 있습니다. 먼저 앱이 응답성을 유지할 수 있도록 하려면 모든 CPU 집약적 및 I/O 작업을 ASTA 스레드에서 실행해서는 안 됩니다. 둘째, 백그라운드 스레드에서 발생하는 결과를 ASTA로 다시 마샬링하여 UI를 업데이트해야 합니다. C++ UWP 앱 MainPage
및 기타 XAML 페이지는 모두 ATSA에서 실행됩니다. 따라서 ASTA에 선언된 작업 연속은 기본적으로 실행되므로 연속 본문에서 직접 컨트롤을 업데이트할 수 있습니다. 그러나 작업을 다른 작업에 중첩하는 경우 중첩된 작업의 모든 연속 작업은 MTA에서 실행됩니다. 따라서 이러한 연속이 실행되는 컨텍스트를 명시적으로 지정할지 여부를 고려해야 합니다.
같은 IAsyncOperation<TResult>
비동기 작업에서 만든 작업은 스레딩 세부 정보를 무시하는 데 도움이 되는 특수 의미 체계를 사용합니다. 작업이 백그라운드 스레드에서 실행될 수 있지만(또는 스레드에서 전혀 지원되지 않을 수 있음) 연속 작업은 연속 작업을 시작한 아파트(즉, 호출 task::then
된 아파트에서)에서 실행되도록 기본적으로 보장됩니다.
동시성::task_continuation_context 클래스를 사용하여 연속 작업의 실행 컨텍스트를 제어할 수 있습니다. 다음 정적 도우미 메서드를 사용하여 개체를 만듭니다 task_continuation_context
.
동시성::task_continuation_context::use_arbitrary 사용하여 연속이 백그라운드 스레드에서 실행되도록 지정합니다.
동시성::task_continuation_context::use_current 사용하여 연속이 호출
task::then
된 스레드에서 실행되도록 지정합니다.
개체를 task_continuation_context
task::then 메서드에 전달하여 연속 작업의 실행 컨텍스트를 명시적으로 제어하거나 작업을 다른 아파트로 전달한 다음 메서드를 호출 task::then
하여 실행 컨텍스트를 암시적으로 제어할 수 있습니다.
중요합니다
UWP 앱의 기본 UI 스레드는 STA에서 실행되므로 기본적으로 해당 STA에서 만드는 연속은 STA에서 실행됩니다. 따라서 MTA에서 만드는 연속은 MTA에서 실행됩니다.
다음 섹션에서는 디스크에서 파일을 읽고, 해당 파일에서 가장 일반적인 단어를 찾은 다음, UI의 결과를 보여 주는 앱을 보여줍니다. UI를 업데이트하는 최종 작업은 UI 스레드에서 발생합니다.
중요합니다
이 동작은 UWP 앱과 관련이 있습니다. 데스크톱 앱의 경우 연속 실행 위치를 제어하지 않습니다. 대신 스케줄러는 각 연속 작업을 실행할 작업자 스레드를 선택합니다.
중요합니다
STA에서 실행되는 이어지는 작업의 본문에서 동시성::task::wait를 호출하지 마세요. 호출하는 경우 이 메서드가 현재 스레드를 차단하고 앱이 응답하지 않게 만들 수 있기 때문에 런타임에서 concurrency::invalid_operation 을 throw합니다. 그러나 concurrency::task::get 메서드를 호출하여 작업 기반 연속에서 선행 작업의 결과를 받을 수 있습니다.
예: C++ 및 XAML을 사용하여 Windows 런타임 앱에서 실행 제어
디스크에서 파일을 읽고, 해당 파일에서 가장 일반적인 단어를 찾은 다음, 결과를 UI에 표시하는 C++ XAML 앱을 고려합니다. 이 앱을 만들려면 Visual Studio에서 빈 앱(유니버설 Windows) 프로젝트를 만든 후 이름을 지정하세요 CommonWords
. 앱 매니페스트에서 문서 라이브러리 기능을 지정하여 앱이 Documents 폴더에 액세스할 수 있도록 합니다. 또한 앱 매니페스트의 선언 섹션에 Text(.txt) 파일 형식을 추가합니다. 앱 기능 및 선언에 대한 자세한 내용은 Windows 앱의 패키징, 배포 및 쿼리를 참조하세요.
MainPage.xaml의 Grid
요소를 업데이트하여 ProgressRing
요소와 TextBlock
요소를 포함합니다. 작업이 ProgressRing
진행 중임을 나타내고 TextBlock
계산 결과를 표시합니다.
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<ProgressRing x:Name="Progress"/>
<TextBlock x:Name="Results" FontSize="16"/>
</Grid>
#include
에 다음 문을 추가합니다.
#include <sstream>
#include <ppltasks.h>
#include <concurrent_unordered_map.h>
클래스(MainPage.h)에 다음 메서드 선언을 MainPage
추가합니다.
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);
다음 using
문을 MainPage.cpp 추가합니다.
using namespace concurrency;
using namespace std;
using namespace Windows::Storage;
using namespace Windows::Storage::Streams;
MainPage.cpp에서 MainPage::MakeWordList
, MainPage::FindCommonWords
, 및 MainPage::ShowResults
메서드를 구현합니다.
MainPage::MakeWordList
MainPage::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
생성자를 수정하여 UI에 책 The Iliad by Homer의 일반적인 단어를 표시하는 연속 작업 체인을 만듭니다. 텍스트를 개별 단어로 분할하고 공통 단어를 찾는 처음 두 연속 작업은 시간이 오래 걸릴 수 있으므로 백그라운드에서 명시적으로 실행되도록 설정됩니다. 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
를 호출한 아파트에서 연속 처리를 실행합니다. 따라서 이 예제에서는 백그라운드 스레드에서 UI를 포함하지 않는 작업을 수행하도록 지정하는 데 사용합니다 task_continuation_context::use_arbitrary
.
다음 그림은 CommonWords
앱의 결과를 보여줍니다.
이 예제에서는 지원하는 개체가 암시적 취소 토큰을 task
사용하므로 취소를 지원할 create_async
수 있습니다. 작업 함수에서 협력적으로 취소에 응답해야 할 경우 cancellation_token
객체를 사용하도록 정의합니다. PPL의 취소에 대한 자세한 내용은 PPL의 취소를 참조하세요.