Поделиться через


Создание асинхронных операций в 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 выполняется в произвольном контексте.

  • Вы можете использовать метод параллелизма::task::then для создания цепочки задач, выполняемых друг за другом. В приложении UWP контекст по умолчанию для продолжения задачи зависит от того, как была создана эта задача. Если задача была создана путем передачи асинхронного действия конструктору задач или передачи лямбда-выражения, возвращающего асинхронное действие, то контекст по умолчанию для всех продолжений этой задачи является текущим контекстом. Если задача не создается из асинхронного действия, произвольный контекст используется по умолчанию для продолжения задачи. Контекст по умолчанию можно переопределить с помощью класса concurrency::task_continuation_context .

В этом документе

Создание асинхронных операций

Модель задачи и продолжения можно использовать в библиотеке параллельных шаблонов (PPL) для определения фоновых задач, а также дополнительных задач, выполняемых при завершении предыдущей задачи. Эта функция предоставляется классом concurrency::task . Дополнительные сведения об этой модели и task классе см. в разделе "Параллелизм задач".

Среда выполнения Windows — это программный интерфейс, который можно использовать для создания приложений UWP, которые выполняются только в специальной операционной среде. Такие приложения используют авторизованные функции, типы данных и устройства, а также распределяются из Microsoft Store. Среда выполнения Windows представлена двоичным интерфейсом приложения (ABI). ABI — это базовый двоичный контракт, который делает API среды выполнения Windows доступными для языков программирования, таких как Visual C++.

Используя среду выполнения Windows, вы можете использовать лучшие функции различных языков программирования и объединить их в одно приложение. Например, вы можете создать пользовательский интерфейс в JavaScript и выполнить логику приложения с интенсивным вычислением в компоненте C++. Возможность выполнения этих вычислительных операций в фоновом режиме является ключевым фактором в сохранении отклика пользовательского интерфейса. 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 предоставляет функцию 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 работает с токенами отмены. При использовании маркера отмены для отмены работы среда выполнения не запускает новую работу, которая подписывается на этот маркер. Работа, которая уже активна, может отслеживать его маркер отмены и остановиться, когда это возможно. Этот механизм подробно описан в документе "Отмена" в 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++ .

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

В цепочке продолжений задач всегда очищайте состояние и затем вызывайте concurrency::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 метод определяет один из четырех асинхронных интерфейсов. Методы, которые возвращают значение, возвращают объект 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::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# Blank App (XAML) для добавления второго проекта в решение Visual Studio. В этом примере проект называется Primes. Затем из Primes проекта добавьте ссылку на PrimesLibrary проект.

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

<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 метод вызывает IAsyncOperationWithProgress<TResult, TProgress>::Cancel , чтобы отменить операцию. Среда выполнения Concurrency, управляющая базовой асинхронной операцией, генерирует внутренний тип исключения, который перехватывается средой выполнения Windows, чтобы сигнализировать о завершении отмены. Дополнительные сведения о модели отмены см. в разделе "Отмена".

Это важно

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

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

Приложение Windows Runtime Primes.

В качестве примера, в котором используется create_async для создания асинхронных задач, которые могут использоваться другими языками, см. «Использование C++ в примере оптимизатора поездки Bing Maps».

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

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

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

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

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

Это важно

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

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

Это важно

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

Это важно

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

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

Рассмотрим приложение XAML C++, которое считывает файл с диска, находит наиболее распространенные слова в этом файле, а затем отображает результаты в пользовательском интерфейсе. Чтобы создать это приложение, начните в Visual Studio с создания проекта пустого универсального приложения Windows, присвоив ему имя CommonWords. В манифесте приложения укажите возможность библиотеки документов, чтобы приложение получило доступ к папке "Документы". Кроме того, добавьте тип файла 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 инструкции в 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 CommonWords.

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

См. также

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