Partager via


Création d'opérations asynchrones en C++ pour les applications Windows Store

Ce document décrit certains des points clés à garder à l'esprit lorsque vous utilisez le Runtime de concurrence pour effectuer des opérations asynchrones dans une application Windows Store.

L'utilisation de la programmation asynchrone est un élément clé du modèle d'application Windows Store car elle permet aux applications de rester réactives aux entrées d'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. Le document Programmation asynchrone en C++ fournit une vue d'ensemble du modèle asynchrone qui est disponible dans Visual C++ pour créer des applications Windows Store. Ce document explique comment consommer et créer à la fois des chaînes d'opérations Windows Runtime asynchrones. Cette section décrit comment utiliser le Runtime de concurrence 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. Pensez également à lire Modèles et conseils de programmation asynchrone dans Hilo (applications du Windows Store en C++ et XAML) pour découvrir comment nous avons utilisé le Runtime de concurrence pour implémenter des opérations asynchrones dans Hilo, une application Windows Store utilisant C++ et XAML.

Notes

Vous pouvez utiliser la Bibliothèque de modèles parallèles (PPL) et la Bibliothèque d'agents asynchrones dans une application Windows Store.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 Runtime de concurrence qui ne sont disponibles que pour les applications Windows Store, et non les applications 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 Windows Store, le contexte par défaut pour les continuations d'une tâche dépend de la manière dont cette tâche a été générée. 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 générée à 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

  • Exemple : Création d'un composant Windows Runtime C++

  • Contrôle du thread d'exécution

  • Exemple : Contrôle de l'exécution dans une application du Windows Store avec C++ et XAML

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, voir Parallélisme des tâches (runtime d'accès concurrentiel).

Windows Runtime est une interface de programmation que vous pouvez utiliser pour créer des applications Windows Store qui fonctionnent uniquement dans un environnement de système d'exploitation spécial. De telles applications utilisent des fonctions, des types de données et des appareils autorisés, et sont distribuées par Windows Store. Le 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++.

Grâce au Windows Runtime, vous pouvez utiliser les meilleures fonctionnalités de différents langages de programmation et les associer dans une 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. Parce que la classe task 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++). Le Windows Runtime fournit quatre interfaces que vous pouvez utiliser pour représenter des opérations asynchrones :

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++, le Runtime de concurrence 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 fonction create_async prend une fonction de travail (généralement une expression lambda), crée en interne un objet task et inclut cette tâche dans une des quatre interfaces asynchrones Windows Runtime.

Notes

N'utilisez create_async que 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 Annulation dans la bibliothèque de modèles parallèles. Vous pouvez connecter de deux façons l'annulation des tâches aux méthodes Windows Runtime Cancel. 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 méthode Cancel est appelée, ce jeton d'annulation est annulé et les règles normales d'annulation s'appliquent à l'objet task sous-jacent 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. La section Exemple : Contrôle de l'exécution dans une Application du Windows Store avec C++ et XAML indique comment effectuer l'annulation dans une application Windows Store avec C# et XAML qui utilise un composant C++ Windows Runtime personnalisé.

Avertissement

Dans une chaîne de continuations de tâches, nettoyez toujours l'état, puis appelez concurrency::cancel_current_task lorsque concurrency::is_task_cancellation_requested renvoie true.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 affiche les différentes façons de créer un objet IAsyncAction 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.
    });
});

[Haut]

Exemple : créer un composant Windows Runtime C++ et le consommer à partir de C#

Imaginons une application qui utilise XAML et C# pour définir l'interface utilisateur et un composant C++ Windows Runtime pour exécuter des opérations de calcul intensif. Dans cet exemple, le composant C++ calcule les nombres premiers inclus dans un intervalle donné. Pour illustrer les différences entre les quatre Windows Runtime interfaces de tâches asynchrones, commencez par créer une Nouvelle solution dans Visual Studio, puis nommez-la 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 Windows::Foundation::Collections::IVector<int>. 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);
    };
}

Notes

Par convention, les noms de méthode asynchrone 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 exécute d'abord la validation pour garantir que les paramètres d'entrée ne sont pas négatifs. Si une valeur d'entrée est négative, la méthode lève Platform::InvalidArgumentException. La gestion des erreurs est expliquée plus loin dans cette section.

Pour utiliser ces méthodes depuis une application Windows Store, utilisez le modèle Application vide (XAML) Visual C# 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="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:Primes"
    xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="https://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 les modèles asynchrones disponibles en C# et en Visual Basic, voir les pages Modèles asynchrones des applications Windows Store en C# et Modèles asynchrones des applications Windows Store en VB.

Les méthodes getPrimesCancellation et cancelGetPrimes opèrent ensemble pour permettre à l'utilisateur d'annuler l'opération. Quand l'utilisateur choisit le bouton Annuler, la méthode cancelGetPrimes appelle IAsyncOperationWithProgress<TResult, TProgress>::Cancel pour annuler l'opération. Le Runtime de concurrence, qui gère l'opération asynchrone sous-jacente, renvoie un type d'exception interne qui est intercepté par le Windows Runtime pour signaler que l'annulation est terminée. Pour plus d'informations sur le modèle d'annulation, voir Annulation dans la bibliothèque de modèles parallèles.

Important

Pour permettre au Runtime de concurrence de signaler correctement au 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, renvoyez l'exception afin de garantir que le Windows Runtime peut effectuer l'opération d'annulation.

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

Application Primes de Windows Store

Pour obtenir des exemples qui utilisent create_async pour créer des tâches asynchrones qui peuvent être utilisées par d'autres langages, voir Utilisation de C++ dans l'exemple de l'optimiseur de voyage Bing Maps et Opérations asynchrones sous Windows 8 en C++ avec la bibliothèque PPL.

[Haut]

Contrôle du thread d'exécution

Le 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 C++ Windows Store, MainPage et toutes les autres pages XAML s'exécutent sur l'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

Puisque le thread d'interface utilisateur des applications Windows Store s'exécute sous un STA, les continuations que vous créez sur ce STA fonctionnent par défaut sur le 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 Windows Store.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.

[Haut]

Exemple : contrôle de l'exécution dans une application Windows Store 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, commencez par créer un projet Windows Store Application vide (XAML) dans Visual Studio, puis nommez-le 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 fonctions et les déclarations d'application, voir Packages et déploiement d'applications.

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 #include suivantes à 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.
    });
}

Notes

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 présente les résultats de l'application CommonWords.

Application CommonWords de Windows Store

Dans cet exemple, il est possible de prendre en charge l'annulation, car les objets task qui prennent en charge l'utilisation decreate_async utilisent un jet 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, voir Annulation dans la bibliothèque de modèles parallèles

[Haut]

Voir aussi

Concepts

Concurrency Runtime