Создание асинхронных операций в C++ для приложений UWP

В этом документе описываются некоторые ключевые моменты, которые следует учитывать при использовании класса задач для создания Windows асинхронных операций на основе ThreadPool в приложении универсальной среда выполнения Windows (UWP).

Использование асинхронного программирования является ключевым компонентом в модели приложений среда выполнения Windows, так как оно позволяет приложениям реагировать на ввод данных пользователем. Можно запустить длительную задачу без блокировки потока ИП и получить результаты выполнения задачи позже. Можно также отменять задачи и получать уведомления о ходе выполнения задач, выполняемых в фоновом режиме. Асинхронное программирование в документе на C++ содержит общие сведения об асинхронном шаблоне, доступном в Visual C++ для создания приложений UWP. В этом документе показано, как использовать и создавать цепочки асинхронных операций среда выполнения Windows. В этом разделе описывается, как использовать типы в ppltasks.h для создания асинхронных операций, которые могут использоваться другим компонентом среда выполнения Windows и как управлять выполнением асинхронной работы. Кроме того, рассмотрите возможность чтения шаблонов асинхронного программирования и советов в Hilo (Windows Приложения Магазина с помощью C++ и XAML), чтобы узнать, как мы использовали класс задач для реализации асинхронных операций в Hilo, среда выполнения Windows приложения с помощью C++ и XAML.

Примечание

Библиотеку параллельных шаблонов (PPL) и библиотеку асинхронных агентов можно использовать в приложении UWP. Однако невозможно использовать планировщик задач или диспетчер ресурсов. В этом документе описываются дополнительные функции, предоставляемые 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 — это базовый двоичный контракт, который делает среда выполнения Windows API доступными для языков программирования, таких как Visual C++.

С помощью среда выполнения Windows можно использовать лучшие функции различных языков программирования и объединить их в одно приложение. Например, можно создать ИП в JavaScript и выполнять трудоемкую вычислительную логику приложения в компоненте, написанном на C++. Возможность выполнять такие ресурсоемкие операции в фоновом режиме является ключевым фактором в обеспечении скорости реагирования ИП. task Так как класс предназначен для C++, необходимо использовать интерфейс среда выполнения Windows для взаимодействия асинхронных операций с другими компонентами (которые могут быть написаны на языках, отличных от C++). Среда выполнения Windows предоставляет четыре интерфейса, которые можно использовать для представления асинхронных операций:

Windows::Foundation::IAsyncAction
Представляет асинхронное действие.

Windows::Foundation::IAsyncActionWithProgress< TProgresss>
Представляет асинхронное действие, сообщающее о ходе выполнения.

Windows::Foundation::IAsyncOperation< Tresult>
Представляет асинхронную операцию, которая возвращает результат.

Windows::Foundation::IAsyncOperationWithProgress< TResult, TProgress>
Возвращает асинхронную операцию, которая возвращает результат и отчитывается о ходе выполнения.

Понятие действие означает, что асинхронная задача не создает значение (представьте функцию, которая возвращает void). Понятие операция означает, что асинхронная задача создает значение. Понятие ход выполнения означает, что задача может отправить сообщение о ходе выполнения вызывающему объекту. Языки JavaScript, .NET Framework и Visual C++ предоставляют свои собственные способы создания экземпляров таких интерфейсов для использования с переходом через границы ABI. Для Visual C++ PPL предоставляет функцию concurrency::create_async . Эта функция создает среда выполнения Windows асинхронное действие или операцию, представляющую завершение задачи. Функция create_async принимает рабочую функцию (обычно лямбда-выражение), внутренне создает task объект и заключает ее в один из четырех асинхронных интерфейсов среда выполнения Windows.

Примечание

Используйте create_async только при создании функциональных возможностей, доступ к которым можно получить с другого языка или другого компонента среда выполнения Windows. Используйте класс task напрямую, если известно, что операция и создается, и используется кодом C++ в том же компоненте.

Возвращаемый тип create_async определяется типом аргументов. Например, если рабочая функция не возвращает значение и не сообщает о ходе выполнения, create_async возвращает IAsyncAction. Если рабочая функция не возвращает значение и сообщает о ходе выполнения, create_async возвращает IAsyncActionWithProgress. Чтобы сообщить о ходе выполнения, укажите объект 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 при необходимости совместно реагировать на отмену в вашей рабочей функции. В разделе "Пример. Управление выполнением в приложении среда выполнения Windows с помощью C++ и XAML" демонстрируется пример выполнения отмены в приложении универсальная платформа Windows (UWP) с помощью C# и XAML, использующего пользовательский компонент среда выполнения Windows C++.

Предупреждение

В цепочке продолжений задач всегда очищайте состояние, а затем вызывайте параллелизм::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.

В следующем примере показаны различные способы создания IAsyncAction объекта, который может использоваться другим компонентом среда выполнения Windows.

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

Пример. Создание компонента среды выполнения Windows на C++ и его использования в коде C#

Рассмотрим приложение, использующее XAML и C# для определения пользовательского интерфейса и компонента среда выполнения Windows C++ для выполнения ресурсоемких операций. В этом примере компонент C++ обнаруживает простые числа в заданном диапазоне. Чтобы проиллюстрировать различия между четырьмя среда выполнения Windows асинхронными интерфейсами задач, начните с Visual Studio, создав пустое решение и назвав егоPrimes. Затем добавьте в решение проект Компонент среды выполнения Windows и назовите его PrimesLibrary. Добавьте следующий код в создаваемый файл заголовка C++ (в примере Class1.h переименовывается в Primes.h). Каждый метод public определяет один из 4 асинхронных интерфейсов. Методы, возвращающие значение, возвращают объект Windows::Foundation::Collections::IVectorint<>. Методы, которые уведомляют о ходе выполнения, генерируют значения 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 обычно заканчиваются асинхронными именами.

Добавьте следующий код в сгенерированный файл с исходным кодом C++ (в примере Class1.cpp переименовывается в Primes.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. Обработка ошибок объясняется далее в этом разделе.

Чтобы использовать эти методы из приложения UWP, используйте шаблон пустого приложения Visual C# (XAML), чтобы добавить второй проект в решение Visual Studio. В этом примере проект называется Primes. Затем из проекта Primes добавьте ссылку на проект PrimesLibrary .

Добавьте следующий код в MainPage.xaml. Этот код определяет пользовательский интерфейс, чтобы можно было вызвать компонент на С++ и вывести результат.

<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 в файле MainPage.xaml. Этот код определяет объект 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 для обновления пользовательского интерфейса после выполнения асинхронных операций. Сведения об асинхронном программировании в приложениях UWP см. в разделе "Потоки и асинхронное программирование".

Методы getPrimesCancellation и cancelGetPrimes работают вместе, позволяя пользователю отменить операцию. Когда пользователь нажимает кнопку "Отмена ", cancelGetPrimes метод вызывает IAsyncOperationWithProgressTResult<, TProgress>::Cancel , чтобы отменить операцию. Среда выполнения параллелизма, управляющая базовой асинхронной операцией, создает внутренний тип исключения, который перехватывается среда выполнения Windows для связи с завершением отмены. Дополнительные сведения о модели отмены см. в разделе "Отмена".

Важно!

Чтобы PPL правильно сообщить среда выполнения Windows, что она отменила операцию, не перехватывайте этот внутренний тип исключения. Это означает, что не нужно перехватывать все исключения (catch (...)). Если необходимо перехватывать все исключения, повторно создайте исключение, чтобы убедиться, что среда выполнения Windows может завершить операцию отмены.

На следующем рисунке показано приложение Primes после выбора каждого параметра.

Windows Runtime Primes app.

Пример создания create_async асинхронных задач, которые могут использоваться другими языками, см. в примере оптимизатора Карты Bing Trip Optimizer с помощью C++.

Управление потоком выполнения

В среда выполнения Windows используется модель потоков COM. В этой модели объекты размещаются в различных подразделениях в зависимости от того, как они обрабатывают свою синхронизацию. Потокобезопасные объекты размещаются в многопотоковых подразделениях (MTA). Объекты, которые должны быть доступны из одного потока, размещаются в однопотоковых подразделениях (STA).

В приложении с пользовательским интерфейсом поток ASTA (STA приложения) отвечает за перенос сообщений окна и является единственным потоком в процессе, который может обновить размещенные в STA элементы управления пользовательского интерфейса. Это имеет два последствия. Во-первых, для быстрого реагирования приложения на ввод пользователя все вычислительно сложные операции и операции ввода-вывода не должны выполняться в потоке ASTA. Во-вторых, результаты, полученные из фоновых потоков, должны маршалироваться обратно в ASTA для обновления пользовательского интерфейса. В приложении MainPage UWP на C++ и других страницах XAML все выполняются в ATSA. Поэтому продолжения задачи, которые объявляются в ASTA, выполняются там по умолчанию, поэтому можно обновлять элементы управления напрямую в теле продолжения. Однако если вложить задачу в другую задачу, все продолжения в этой вложенной задаче выполняются в MTA. Поэтому необходимо рассмотреть, требуется ли явно указать, в каком контексте выполняются эти продолжения.

Задачи, которые создаются из асинхронной операции, такие как IAsyncOperation<TResult>, используют специальную семантику, которая поможет игнорировать детали многопоточной реализации. Хотя операция может выполняться в фоновом потоке (или может совсем не обеспечиваться потоком), гарантируется выполнение ее продолжений по умолчанию в подразделении, начавшем операции продолжения (другими словами, из подразделения, вызвавшего task::then). Можно использовать класс concurrency::task_continuation_context для управления контекстом выполнения продолжения. Используйте эти статические вспомогательные методы для создания объектов task_continuation_context .

Можно передать объект task_continuation_context в метод task::then , чтобы явно управлять контекстом выполнения продолжения, или можно передать задачу в другое подразделение и затем вызвать метод task::then для неявного управления контекстом выполнения.

Важно!

Так как основной поток пользовательского интерфейса приложений UWP выполняется в STA, продолжения, созданные на этом STA по умолчанию, выполняются в STA. Соответственно, продолжения, созданные в MTA, выполняются в MTA.

В следующем разделе показано приложение, которое считывает файл с диска, находит наиболее распространенные слова в этом файле, а затем отображает результаты в пользовательском интерфейсе. Последняя операция, обновление пользовательского интерфейса, происходит в потоке ИП.

Важно!

Это поведение зависит от приложений UWP. В приложениях для настольных систем не требуется контролировать, где выполняются продолжения. Вместо этого планировщик выбирает рабочий поток, в котором будет выполняться каждое продолжение.

Важно!

Не вызывайте concurrency::task::wait в теле продолжения, выполняемого в STA. В противном случае среда выполнения создает concurrency::invalid_operation так как этот метод блокирует текущий поток и может вызвать зависание приложения. Тем не менее можно вызвать метод concurrency::task::get для получения результата из предшествующей задачи в потоке задач.

Пример. Управление выполнением в приложении среда выполнения Windows с помощью C++ и XAML

Рассмотрим приложение C++ XAML, которое считывает файл с диска, находит наиболее распространенные слова в этом файле, а затем отображает результаты в пользовательском интерфейсе. Чтобы создать это приложение, начните с Visual Studio, создав проект пустого приложения (универсального Windows) и назвав егоCommonWords. В манифесте приложения укажите возможность Библиотека документов , которая позволяет приложению обращаться к папке "Документы". Также добавьте текстовый тип файла (TXT) в раздел объявлений манифеста приложения. Дополнительные сведения о возможностях и объявлениях приложений см. в статье "Упаковка, развертывание и запрос приложений Windows".

Обновите элемент Grid в MainPage.xaml для включения элемента ProgressRing и элемента TextBlock . ProgressRing показывает, что операция выполняется, а TextBlock отображает результаты вычислений.

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

Добавьте следующие #include инструкции в файл pch.h.

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

Добавьте следующие выражения 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 отображает результат вычисления в пользовательском интерфейсе.

// 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 для создания цепочки задач продолжения, которые будут отображать в пользовательском интерфейсе распространенные слова из книги Гомера Илиада . Первые две задачи продолжения, которые разделят текст на отдельные слова и найдут распространенные слова, могут занимать продолжительное время, поэтому для них явно задано выполнение в фоновом режиме. Последняя задача продолжения, которая обновляет пользовательский интерфейс, не определяет контекст продолжения, поэтому следует правилам потоковых подразделений.

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 для указания того, что операции, которые не используют пользовательский интерфейс, должны выполняться в фоновом потоке.

На следующем рисунке показаны результаты выполнения приложения CommonWords .

Windows Runtime CommonWords app.

В этом примере можно поддерживать отмену, так как task объекты, поддерживающие create_async использование неявного маркера отмены. Определите свою рабочую функцию для приема объекта cancellation_token , если задачи должны отвечать на отмену в режиме совместной работы. Дополнительные сведения об отмене в PPL см. в разделе Cancellation in the PPL.

См. также раздел

Среда выполнения с параллелизмом