Création d’opérations asynchrones dans C++ pour les applications UWP

Ce document décrit certains des points clés à garder à l’esprit lorsque vous utilisez la classe de tâches pour produire des opérations asynchrones basées sur Windows ThreadPool dans une application UWP (Universal Windows Runtime).

L’utilisation de la programmation asynchrone est un composant clé dans le modèle d’application Windows Runtime, car elle permet aux applications de rester réactives à l’entrée utilisateur. Vous pouvez commencer une tâche de longue durée sans bloquer le thread d'interface utilisateur, et vous pouvez recevoir ultérieurement les résultats de la tâche. Vous pouvez également annuler des tâches et de recevoir des notifications de progression comme les tâches sont exécutées en arrière-plan. La programmation asynchrone du document en C++ fournit une vue d’ensemble du modèle asynchrone disponible dans Visual C++ pour créer des applications UWP. Ce document explique comment consommer et créer des chaînes d’opérations Windows Runtime asynchrones. Cette section explique comment utiliser les types dans ppltasks.h pour produire des opérations asynchrones qui peuvent être consommées par un autre composant Windows Runtime et comment contrôler l’exécution du travail asynchrone. Envisagez également de lire des modèles de programmation asynchrones et des conseils dans Hilo (applications du Windows Store utilisant C++ et XAML) pour savoir comment nous avons utilisé la classe de tâches pour implémenter des opérations asynchrones dans Hilo, une application Windows Runtime à l’aide de C++ et XAML.

Remarque

Vous pouvez utiliser la bibliothèque de modèles parallèles (PPL) et la bibliothèque d’agents asynchrones dans une application UWP. Toutefois, vous ne pouvez pas utiliser le planificateur de tâches, ni le Gestionnaire des ressources. Ce document décrit les fonctionnalités supplémentaires fournies par le PPL qui sont disponibles uniquement pour une application UWP, et non pour une application de bureau.

Points clés

  • Utilisez concurrency::create_async pour créer des opérations asynchrones qui peuvent être utilisées par d'autres composants (qui peuvent être écrits dans d'autres langages que C++).

  • Utilisez concurrency::progress_reporter pour rapporter des notifications de progression aux composants qui appellent vos opérations asynchrones.

  • Utilisez les jetons d'annulation pour permettre l'annulation des opérations asynchrones internes.

  • Le comportement de la fonction create_async dépend du type de retour de la fonction de travail passée. Une fonction de travail qui retourne une tâche ( task<T> ou task<void>) s'exécute de manière synchrone dans le contexte qui a appelé create_async. Une fonction de travail qui retourne T ou void s'exécute dans un contexte arbitraire.

  • Il est possible d'utiliser la méthode concurrency::task::then pour créer une chaîne de tâches qui s'exécutent l'une après l'autre. Dans une application UWP, le contexte par défaut des continuations d’une tâche dépend de la façon dont cette tâche a été construite. Si la tâche a été créée en passant une action asynchrone au constructeur de tâche, ou en passant une expression lambda qui retourne une action asynchrone, le contexte par défaut pour toutes les continuations de cette tâche est le contexte actuel. Si la tâche n’est pas construite à partir d’une action asynchrone, un contexte arbitraire est utilisé par défaut pour les continuations de la tâche. Il est possible de substituer le contexte par défaut avec la classe concurrency::task_continuation_context .

Dans ce document

Création d'opérations asynchrones

Vous pouvez utiliser la tâche et le modèle de continuation dans la bibliothèque de modèles parallèles (PPL) pour définir des tâches en arrière-plan ainsi que des tâches supplémentaires qui s'exécutent lorsque les tâches précédentes se terminent. Cette fonctionnalité est fournie par la classe concurrency::task . Pour plus d’informations sur ce modèle et la classe task , consultez Task Parallelism.

Windows Runtime est une interface de programmation que vous pouvez utiliser pour créer des applications UWP qui s’exécutent uniquement dans un environnement de système d’exploitation spécial. Ces applications utilisent des fonctions autorisées, des types de données et des appareils, et sont distribuées à partir du Microsoft Store. Windows Runtime est représenté par l’interface binaire d’application (ABI). L’ABI est un contrat binaire sous-jacent qui rend les API Windows Runtime disponibles pour les langages de programmation tels que Visual C++.

À l’aide de Windows Runtime, vous pouvez utiliser les meilleures fonctionnalités de différents langages de programmation et les combiner en une seule application. Par exemple, vous pouvez créer votre interface utilisateur dans JavaScript et exécuter la logique d'application nécessitant de nombreuses ressources de calcul dans un composant C++. La capacité à exécuter ces opérations nécessitant de nombreuses ressources de calcul en arrière-plan est un facteur clé pour que votre interface utilisateur reste réactive. Étant donné que la task classe est spécifique à C++, vous devez utiliser une interface Windows Runtime pour communiquer des opérations asynchrones à d’autres composants (qui peuvent être écrits dans des langages autres que C++). Windows Runtime fournit quatre interfaces que vous pouvez utiliser pour représenter des opérations asynchrones :

Windows::Foundation::IAsyncAction
Représente une opération asynchrone.

Windows ::Foundation ::IAsyncActionWithProgress<TProgress>
Représente une action asynchrone qui rapporte la progression.

Windows ::Foundation ::IAsyncOperation<TResult>
Représente une opération asynchrone qui retourne une valeur.

Windows ::Foundation ::IAsyncOperationWithProgress<TResult, TProgress>
Représente une opération asynchrone qui retourne un résultat et signale une progression.

La notion d' action signifie que la tâche asynchrone ne produit pas de valeur (pensez à une fonction qui retourne void). La notion d' opération signifie que la tâche asynchrone produit une valeur. La notion de progression signifie que la tâche peut rapporter des messages de progression à l'appelant. JavaScript, .NET Framework et Visual C++ disposent chacun de leur propre façon de créer des instances de ces interfaces à utiliser dans le cadre d'ABI. Pour Visual C++, la bibliothèque de modèles parallèles fournit la fonction concurrency::create_async . Cette fonction crée une action ou une opération asynchrone Windows Runtime qui représente l’achèvement d’une tâche. La create_async fonction prend une fonction de travail (généralement une expression lambda), crée en interne un task objet et encapsule cette tâche dans l’une des quatre interfaces Windows Runtime asynchrones.

Remarque

Utilisez create_async uniquement lorsque vous devez créer des fonctionnalités accessibles à partir d’un autre langage ou d’un autre composant Windows Runtime. Utilisez la classe task directement lorsque vous savez que l'opération est à la fois produite et consommée par du code C++ dans le même composant.

Le type de retour de create_async est déterminé par le type de ses arguments. Par exemple, si votre fonction de travail ne retourne pas de valeur et ne rapporte pas la progression, create_async renvoie IAsyncAction. Si votre fonction de travail ne renvoie pas de valeur et rapporte également la progression, create_async renvoie IAsyncActionWithProgress. Pour signaler la progression, utilisez un objet concurrency::progress_reporter comme paramètre de votre fonction de travail. La possibilité de signaler la progression vous permet de rapporter la quantité de travail qui a été effectuée et la quantité restante (par exemple sous forme d'un pourcentage). Elle vous permet également de rapporter les résultats à mesure qu'ils deviennent disponibles.

Les interfaces IAsyncAction, IAsyncActionWithProgress<TProgress>, IAsyncOperation<TResult>et IAsyncActionOperationWithProgress<TProgress, TProgress> fournissent chacune une méthode Cancel qui vous permet d'annuler l'opération asynchrone. La classe task fonctionne avec des jetons d'annulation. Lorsque vous utilisez un jeton d'annulation pour annuler un travail, le runtime ne démarre pas le nouveau processus qui souscrit à ce jeton. Un travail qui est déjà actif peut contrôler son jeton d'annulation et s'arrêter lorsqu'il peut. Ce mécanisme est décrit plus en détail dans le document Cancellation in the PPL. Vous pouvez connecter l’annulation des tâches avec les méthodes Windows Runtime Cancel de deux façons. Tout d'abord, vous pouvez définir la fonction de travail que vous passez à create_async pour prendre un objet concurrency::cancellation_token . Lorsque la Cancel méthode est appelée, ce jeton d’annulation est annulé et les règles d’annulation normales s’appliquent à l’objet sous-jacent task qui prend en charge l’appel create_async . Si vous ne fournissez pas d'objet cancellation_token , l'objet task sous-jacent en définit un implicitement. Définissez un objet cancellation_token lorsque vous devez gérer de manière coopérative l'annulation dans votre fonction de travail. L’exemple de section : Contrôle de l’exécution dans une application Windows Runtime avec C++ et XAML montre un exemple d’annulation dans une application plateforme Windows universelle (UWP) avec C# et XAML qui utilise un composant C++ Windows Runtime personnalisé.

Avertissement

Dans une chaîne de continuations de tâches, propre toujours à l’état du haut, puis appelez concurrency ::cancel_current_task lorsque le jeton d’annulation est annulé. Si vous utilisez la méthode du retour rapide au lieu d'appeler cancel_current_task, l'opération passe à l'état Terminé au lieu de l'état Annulé.

Le tableau suivant résume les combinaisons qu'il est possible d'utiliser pour définir des opérations asynchrones dans votre application.

Pour créer cette interface Windows Runtime Retournez ce type à partir de create_async Passez ces types de paramètre à votre fonction de travail pour utiliser un jeton implicite d'annulation Passez ces types de paramètre à votre fonction de travail pour utiliser un jeton explicite d'annulation
IAsyncAction void ou task<void> (aucun) (cancellation_token)
IAsyncActionWithProgress<TProgress> void ou task<void> (progress_reporter) (progress_reporter, cancellation_token)
IAsyncOperation<TResult> T ou task<T> (aucun) (cancellation_token)
IAsyncActionOperationWithProgress<TProgress, TProgress> T ou task<T> (progress_reporter) (progress_reporter, cancellation_token)

Vous pouvez retourner une valeur ou un task objet à partir de la fonction de travail que vous passez à la fonction create_async . Ces variations produisent différents comportements. Lorsque vous retournez une valeur, la fonction de travail est encapsulée dans un task afin qu'elle puisse être exécutée sur un thread d'arrière-plan. En outre, la task sous-jacente utilise un jeton implicite d'annulation. Inversement, si vous retournez un objet task , la fonction de travail s'exécute de façon synchrone. Par conséquent, si vous retournez un objet task , vérifiez que toutes les opérations de longue durée de votre fonction de travail s'exécutent également comme tâches afin de permettre à votre application de rester réactive. En outre, la task sous-jacente n'utilise pas de jeton implicite d'annulation. Par conséquent, vous devez définir votre fonction de travail pour prendre un objet cancellation_token si vous avez besoin de la prise en charge de l'annulation lorsque vous retournez un objet task depuis create_async.

L’exemple suivant montre les différentes façons de créer un IAsyncAction objet qui peut être consommé par un autre composant Windows Runtime.

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

Exemple : création d’un composant Windows Runtime C++ et consommation à partir de C#

Considérez une application qui utilise XAML et C# pour définir l’interface utilisateur et un composant Windows Runtime C++ pour effectuer des opérations nécessitant beaucoup de ressources de calcul. Dans cet exemple, le composant C++ calcule les nombres premiers inclus dans un intervalle donné. Pour illustrer les différences entre les quatre interfaces de tâches asynchrones Windows Runtime, démarrez, dans Visual Studio, en créant une solution vide et en la nommant Primes. Ajoutez ensuite à la solution un projet Composant Windows Runtime et nommez-le PrimesLibrary. Ajoutez le code suivant au fichier d'en-tête généré en C++ (cet exemple renomme Class1.h en Primes.h). Chaque méthode public définit une des quatre interfaces asynchrones. Les méthodes qui retournent une valeur retournent un objet Int> Windows ::Foundation ::Collections ::IVector<. Les méthodes qui signalent la progression génèrent des valeurs double qui définissent le pourcentage de travail global effectué.

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

Remarque

Par convention, les noms de méthodes asynchrones dans Windows Runtime se terminent généralement par « Async ».

Ajoutez le code suivant au fichier source généré en C++ (cet exemple renomme Class1.cpp en Primes.cpp). La fonction is_prime détermine si son entrée est un nombre premier. Les autres méthodes implémentent la classe Primes . Chaque appel à create_async utilise une signature compatible avec la méthode par laquelle elle est appelée. Par exemple, étant donné que Primes::ComputePrimesAsync renvoie IAsyncAction, la fonction de travail fournie à create_async ne retourne pas de valeur, ni ne prend d'objet progress_reporter comme paramètre.

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

Chaque méthode effectue d’abord la validation pour s’assurer que les paramètres d’entrée ne sont pas négatifs. Si une valeur d’entrée est négative, la méthode génère Platform::InvalidArgumentException. La gestion des erreurs est expliquée plus loin dans cette section.

Pour utiliser ces méthodes à partir d’une application UWP, utilisez le modèle XAML (Visual C# Blank App) pour ajouter un deuxième projet à la solution Visual Studio. Cet exemple nomme le projet Primes. Ensuite, à partir du projet Primes , ajoutez une référence au projet PrimesLibrary .

Ajoutez le code suivant à MainPage.xaml. Ce code définit l'interface utilisateur afin que vous puissiez appeler le composant C++ et afficher les résultats.

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

Ajoutez le code suivant à la classe MainPage dans MainPage.xaml. Ce code définit un objet Primes et le bouton des gestionnaires d'événements.

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

Ces méthodes utilisent async et les mots clés await pour mettre à jour l'interface utilisateur une fois les opérations asynchrones terminées. Pour plus d’informations sur le codage asynchrone dans les applications UWP, consultez Threading et programmation asynchrone.

Les méthodes getPrimesCancellation et cancelGetPrimes opèrent ensemble pour permettre à l'utilisateur d'annuler l'opération. Lorsque l’utilisateur choisit le bouton Annuler , la cancelGetPrimes méthode appelle IAsyncOperationWithProgress<TResult, TProgress> ::Cancel pour annuler l’opération. Le runtime d’accès concurrentiel, qui gère l’opération asynchrone sous-jacente, lève un type d’exception interne intercepté par Windows Runtime pour communiquer que cette annulation est terminée. Pour plus d’informations sur le modèle d’annulation, consultez Annulation.

Important

Pour permettre au PPL de signaler correctement à Windows Runtime qu’il a annulé l’opération, n’interceptez pas ce type d’exception interne. Cela signifie que vous ne devez pas non plus intercepter toutes les exceptions (catch (...)). Si vous devez intercepter toutes les exceptions, relancez l’exception pour vous assurer que Windows Runtime peut effectuer l’opération d’annulation.

L’illustration suivante montre l’application Primes après la sélection de chaque option.

Windows Runtime Primes app.

Pour obtenir un exemple qui utilise create_async pour créer des tâches asynchrones qui peuvent être consommées par d’autres langages, consultez Utilisation de C++ dans l’exemple Bing Cartes Trip Optimizer.

Contrôle du thread d'exécution

Windows Runtime utilise le modèle de thread COM. Dans ce modèle, les objets sont hébergés dans des cloisonnements différents, selon la façon dont ils gèrent leur synchronisation. Les objets thread-safe sont hébergés dans des multithreads cloisonnés (MTA). Les objets accessibles par un thread unique sont hébergés dans un thread cloisonné (STA).

Dans une application ayant une interface utilisateur, le thread ASTA est chargé de pomper des messages de fenêtre et est le seul thread dans le processus pouvant mettre à jour les contrôles d'interface utilisateur hébergés par le STA. Cela a deux conséquences. D'abord, pour permettre à l'application de rester réactive, toutes les opérations faisant un usage intensif de l'unité centrale et les opérations d'E/S ne doivent pas être exécutées sur le thread ASTA. Ensuite, les résultats provenant des threads d'arrière-plan doit être marshalés vers l'ASTA pour mettre à jour l'interface utilisateur. Dans une application MainPage UWP C++ et d’autres pages XAML, toutes s’exécutent sur ATSA. Par conséquent, les continuations de tâches déclarées sur l'ASTA sont exécutées par défaut afin de vous permettre de mettre à jour les contrôles directement dans le corps de continuation. Toutefois, si vous imbriquez une tâche dans une autre tâche, toutes les continuations de cette tâche imbriquée s'exécutent dans le MTA. Par conséquent, vous devez déterminer si vous voulez spécifier explicitement dans quel contexte ces continuations s'exécutent.

Une tâche créée à partir d'une opération asynchrone, comme par exemple IAsyncOperation<TResult>, utilise une sémantique spéciale qui vous permet d'ignorer les détails de threading. Même si une opération peut fonctionner sur un thread d'arrière-plan (ou ne peut reposer sur aucun thread), ses continuations sont garanties par défaut pour fonctionner sur le cloisonnement qui a démarré les opérations de continuation (en d'autres termes, depuis le cloisonnement ayant appelé task::then). Vous pouvez utiliser la classe concurrency::task_continuation_context pour contrôler le contexte d'exécution d'une continuation. Utilisez les méthodes statiques d'assistance pour créer des objets task_continuation_context .

Vous pouvez passer un objet task_continuation_context à la méthode task::then pour contrôler explicitement le contexte d'exécution de la continuation ou vous pouvez passer la tâche à un autre cloisonnement et appeler la méthode task::then pour contrôler implicitement le contexte d'exécution.

Important

Étant donné que le thread d’interface utilisateur principal des applications UWP s’exécute sous STA, les continuations que vous créez sur cette sta par défaut s’exécutent sur l’application STA. Par conséquent, les continuations que vous créez dans le MTA s'exécutent sur le MTA.

La section suivante présente une application qui lit un fichier sur le disque dur, recherche les mots les plus courants dans ce fichier, et donne les résultats dans l'interface utilisateur. L'opération finale, mettant à jour l'interface utilisateur, se produit sur le thread d'interface utilisateur.

Important

Ce comportement est spécifique aux applications UWP. Pour les applications de bureau, vous ne contrôlez pas où s'exécutent les continuations. À la place, le planificateur choisit un thread de travail sur lequel exécuter chaque continuation.

Important

N'appelez pas concurrency::task::wait dans le corps d'une continuation qui s'exécute sur le STA. Sinon, le runtime lève concurrency::invalid_operation , car cette méthode bloque le thread actuel et peut provoquer le blocage de l'application. Toutefois, vous pouvez appeler la méthode concurrency::task::get pour recevoir le résultat de la tâche précédente dans une continuation basée sur des tâches.

Exemple : contrôle de l’exécution dans une application Windows Runtime avec C++ et XAML

Considérez une application C++ XAML qui lit un fichier sur le disque, recherche les mots les plus courants dans ce fichier, et donne les résultats dans l'interface utilisateur. Pour créer cette application, démarrez, dans Visual Studio, en créant un projet d’application vide (Windows universel) et en le nommant CommonWords. Dans votre manifeste d'application, spécifiez la Bibliothèque de documents pour permettre à l'application d'accéder au dossier Documents. Ajoutez également le type de fichier texte (.txt) dans la section des déclarations du manifeste d'application. Pour plus d’informations sur les fonctionnalités et déclarations des applications, consultez Empaquetage, déploiement et requête des applications Windows.

Mettez l'élément Grid à jour dans MainPage.xaml pour inclure un élément ProgressRing et un élément TextBlock . Le ProgressRing indique que l'opération est en cours et le TextBlock donne les résultats du calcul.

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

Ajoutez les instructions suivantes #include à pch.h.

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

Ajoutez les déclarations de méthode suivante à la classe 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);

Ajoutez les instructions using suivantes à MainPage.cpp.

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

Dans MainPage.cpp, implémentez les méthodes MainPage::MakeWordList, MainPage::FindCommonWords, et MainPage::ShowResults . MainPage::MakeWordList et MainPage::FindCommonWords exécutent des opérations nécessitant de nombreuses ressources de calcul. La méthode MainPage::ShowResults affiche le résultat du calcul dans l'interface utilisateur.

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

Modifiez le constructeur de MainPage pour créer une chaîne des tâches de continuation qui affiche dans l'interface utilisateur les mots récurrents du livre d'Homère, l'Iliade . Les deux premières tâches de continuation, qui fractionnent le texte en mots individuels et recherchent les mots récurrents, peuvent prendre du temps et sont donc explicitement définies pour s'exécuter en arrière-plan. La tâche de continuation finale, qui met à jour l’interface utilisateur, ne spécifie aucun contexte de continuation, et suit par conséquent des règles de thread de cloisonnement.

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

Remarque

Cet exemple montre comment spécifier des contextes d'exécution et comment utiliser une chaîne de continuations. Il est important de noter que, par défaut, une tâche créée lors une opération asynchrone exécute ses continuations sur le cloisonnement qui a appelé task::then. Par conséquent, cet exemple utilise task_continuation_context::use_arbitrary pour spécifier que les opérations qui n'impliquent pas l'interface utilisateur sont exécutées sur un thread d'arrière-plan.

L’illustration suivante montre les résultats de l’application CommonWords .

Windows Runtime CommonWords app.

Dans cet exemple, il est possible de prendre en charge l’annulation, car les task objets qui prennent en charge create_async utilisent un jeton d’annulation implicite. Définissez la fonction de travail pour prendre un objet cancellation_token si vos tâches doivent répondre à l'annulation de manière coopérative. Pour plus d’informations sur l’annulation dans la bibliothèque PPL, consultez Cancellation in the PPL

Voir aussi

Concurrency Runtime