Delen via


Asynchrone bewerkingen maken in C++ voor UWP-apps

In dit document worden enkele belangrijke punten beschreven die u in gedachten moet houden wanneer u de taakklasse gebruikt om asynchrone bewerkingen op basis van Windows ThreadPool te produceren in een UWP-app (Universal Windows Runtime).

Het gebruik van asynchrone programmering is een belangrijk onderdeel in het Windows Runtime-app-model, omdat apps responsief kunnen blijven op gebruikersinvoer. U kunt een langlopende taak starten zonder de UI-thread te blokkeren en u kunt de resultaten van de taak later ontvangen. U kunt ook taken annuleren en voortgangsmeldingen ontvangen wanneer taken op de achtergrond worden uitgevoerd. Het document Asynchrone programmering in C++ biedt een overzicht van het asynchrone patroon dat beschikbaar is in Visual C++ om UWP-apps te maken. In dit document leert u hoe u zowel ketens van asynchrone Windows Runtime-bewerkingen gebruikt als maakt. In deze sectie wordt beschreven hoe u de typen in ppltasks.h gebruikt om asynchrone bewerkingen te produceren die kunnen worden gebruikt door een ander Windows Runtime-onderdeel en hoe u bepaalt hoe asynchroon werk wordt uitgevoerd. U kunt ook Async-programmeerpatronen en tips lezen in Hilo (Windows Store-apps met C++ en XAML) om te leren hoe we de taakklasse hebben gebruikt voor het implementeren van asynchrone bewerkingen in Hilo, een Windows Runtime-app met C++ en XAML.

Opmerking

U kunt de bibliotheek met parallelle patronen (PPL) en Asynchrone agents in een UWP-app gebruiken. U kunt echter geen Taakplanner of Resource Manager gebruiken. In dit document worden aanvullende functies beschreven die de PPL biedt die alleen beschikbaar zijn voor een UWP-app en niet voor een desktop-app.

Belangrijkste punten

  • Gelijktijdigheid gebruiken::create_async om asynchrone bewerkingen te maken die kunnen worden gebruikt door andere onderdelen (die kunnen worden geschreven in andere talen dan C++).

  • Gebruik gelijktijdigheid::progress_reporter om voortgangsrapportages te verzenden naar onderdelen die uw asynchrone bewerkingen aanroepen.

  • Gebruik annuleringstokens om interne asynchrone bewerkingen te kunnen annuleren.

  • Het gedrag van de create_async functie is afhankelijk van het retourtype van de werkfunctie die eraan wordt doorgegeven. Een werkfunctie die een taak retourneert (hetzij task<T> of task<void>), wordt synchroon uitgevoerd in de context die create_async heeft aangeroepen. Een functie die T of void retourneert, wordt uitgevoerd in een willekeurige context.

  • U kunt de concurrency::task::then-methode gebruiken om een keten van taken te maken die op elkaar volgen. In een UWP-app is de standaardcontext voor de voortzettingen van een taak afhankelijk van de wijze waarop die taak is samengesteld. Als de taak is gemaakt door een asynchrone actie door te geven aan de taakconstructor of door een lambda-expressie door te geven die een asynchrone actie retourneert, is de standaardcontext voor alle vervolgen van die taak de huidige context. Als de taak niet is samengesteld vanuit een asynchrone actie, wordt standaard een willekeurige context gebruikt voor de vervolgbewerkingen van de taak. U kunt de standaardcontext overschrijven met de klasse concurrency::task_continuation_context.

In dit document

Asynchrone bewerkingen maken

U kunt het taak- en vervolgmodel in de PPL (Parallel Patterns Library) gebruiken om achtergrondtaken en aanvullende taken te definiëren die worden uitgevoerd wanneer de vorige taak is voltooid. Deze functionaliteit wordt geleverd door de gelijktijdigheid::taakklasse . Zie task voor meer informatie over dit model en de klasse.

Windows Runtime is een programmeerinterface die u kunt gebruiken om UWP-apps te maken die alleen worden uitgevoerd in een speciale besturingssysteemomgeving. Dergelijke apps gebruiken geautoriseerde functies, gegevenstypen en apparaten en worden gedistribueerd vanuit de Microsoft Store. De Windows Runtime wordt vertegenwoordigd door de ABI (Application Binary Interface ). De ABI is een onderliggend binair contract dat Windows Runtime-API's beschikbaar maakt voor programmeertalen zoals Visual C++.

Met behulp van Windows Runtime kunt u de beste functies van verschillende programmeertalen gebruiken en deze combineren in één app. U kunt bijvoorbeeld uw gebruikersinterface maken in JavaScript en de rekenintensieve app-logica uitvoeren in een C++-onderdeel. De mogelijkheid om deze rekenintensieve bewerkingen op de achtergrond uit te voeren, is een belangrijke factor om uw gebruikersinterface responsief te houden. Omdat de task klasse specifiek is voor C++, moet u een Windows Runtime-interface gebruiken om asynchrone bewerkingen te communiceren met andere onderdelen (die kunnen worden geschreven in andere talen dan C++). Windows Runtime biedt vier interfaces die u kunt gebruiken om asynchrone bewerkingen weer te geven:

Windows::Foundation::IAsyncAction
Vertegenwoordigt een asynchrone actie.

Windows::Foundation::IAsyncActionWithProgress<TProgress>
Vertegenwoordigt een asynchrone actie die de voortgang rapporteert.

Windows::Foundation::IAsyncOperation<TResult>
Vertegenwoordigt een asynchrone bewerking die een resultaat retourneert.

Windows::Foundation::IAsyncOperationWithProgress<TResult, TProgress>
Vertegenwoordigt een asynchrone bewerking die een resultaat retourneert en de voortgang rapporteert.

Het begrip van een actie betekent dat de asynchrone taak geen waarde produceert (denk aan een functie die retourneert void). Het begrip van een bewerking betekent dat de asynchrone taak een waarde produceert. Het begrip voortgang betekent dat de taak voortgangsberichten aan de beller kan rapporteren. JavaScript, .NET Framework en Visual C++ bieden elk een eigen manier om exemplaren van deze interfaces te maken voor gebruik over de ABI-grens. Voor Visual C++ biedt de PPL de concurrency::create_async-functie. Met deze functie wordt een asynchrone actie of bewerking van Windows Runtime gemaakt die de voltooiing van een taak aangeeft. De create_async functie gebruikt een werkfunctie (meestal een lambda-expressie), maakt intern een task object en verpakt die taak in een van de vier asynchrone Windows Runtime-interfaces.

Opmerking

Alleen gebruiken create_async wanneer u functionaliteit moet maken die toegankelijk is vanuit een andere taal of een ander Windows Runtime-onderdeel. Gebruik de task klasse rechtstreeks wanneer u weet dat de bewerking wordt geproduceerd en gebruikt door C++-code in hetzelfde onderdeel.

Het retourtype van create_async wordt bepaald door het type van zijn argumenten. Als uw werkfunctie bijvoorbeeld geen waarde retourneert en de voortgang niet rapporteert, retourneert create_asyncIAsyncAction. Als uw werkfunctie geen waarde retourneert en ook voortgang rapporteert, retourneert create_asyncIAsyncActionWithProgress. Als u de voortgang wilt rapporteren, geeft u een gelijktijdigheid::progress_reporter object op als parameter voor uw werkfunctie. Met de mogelijkheid om voortgang te rapporteren, kunt u rapporteren welke hoeveelheid werk is uitgevoerd en welke hoeveelheid nog steeds resteert (bijvoorbeeld als percentage). Hiermee kunt u ook resultaten rapporteren zodra ze beschikbaar komen.

De IAsyncAction, IAsyncActionWithProgress<TProgress>, IAsyncOperation<TResult> en IAsyncActionOperationWithProgress<TProgress, TProgress> interface bieden ieder een Cancel-methode waarmee u de asynchrone bewerking kunt annuleren. De task klasse werkt met annuleringstokens. Wanneer u een annuleringstoken gebruikt om werk te annuleren, start de runtime geen nieuw werk dat is geabonneerd op dat token. Werk dat al actief is, kan het annuleringstoken controleren en stoppen wanneer dat kan. Dit mechanisme wordt uitgebreid beschreven in het document Annulering in de PPL. U kunt taakannulering op twee manieren verbinden met de Windows Runtime-methoden Cancel . Eerst kunt u de werkfunctie definiëren die u doorgeeft aan create_async om een concurrency::cancellation_token-object te nemen. Wanneer de Cancel methode wordt aangeroepen, wordt dit annuleringstoken geannuleerd en zijn de normale annuleringsregels van toepassing op het onderliggende task object dat de create_async aanroep ondersteunt. Als u geen cancellation_token object opgeeft, definieert het onderliggende task object er impliciet een. Definieer een cancellation_token object wanneer u gezamenlijk moet reageren op annulering in uw werkfunctie. In het gedeelte Voorbeeld: Uitvoering beheren in een Windows Runtime-app met C++ en XAML ziet u een voorbeeld van het uitvoeren van annuleringen in een UWP-app (Universal Windows Platform) met C# en XAML die gebruikmaakt van een aangepast Windows Runtime C++-onderdeel.

Waarschuwing

In een keten van taakvervolgingen moet u altijd de status opschonen en vervolgens concurrency::cancel_current_task aanroepen wanneer de annuleringssleutel geannuleerd wordt. Als u vroeg terugkeert in plaats van aanroepen cancel_current_task, gaat de bewerking over naar de voltooide status in plaats van de geannuleerde status.

De volgende tabel bevat een overzicht van de combinaties die u kunt gebruiken om asynchrone bewerkingen in uw app te definiëren.

Om deze Windows Runtime-interface te maken Retourneer dit type van create_async Geef deze parametertypen door aan uw werkfunctie om een impliciet annuleringstoken te gebruiken Geef deze parametertypen door aan uw werkfunctie om een expliciet annuleringstoken te gebruiken
IAsyncAction void of task<void> (geen) (cancellation_token)
IAsyncActionWithProgress<TProgress> void of task<void> (progress_reporter) (progress_reporter, cancellation_token)
IAsyncOperation<TResult> T of task<T> (geen) (cancellation_token)
IAsyncActionOperationWithProgress<TProgress, TProgress> T of task<T> (progress_reporter) (progress_reporter, cancellation_token)

U kunt een waarde of een task object retourneren uit de werkfunctie die u aan de create_async functie doorgeeft. Deze variaties produceren verschillende gedragingen. Wanneer u een waarde retourneert, wordt de werkfunctie verpakt in een task waarde, zodat deze kan worden uitgevoerd op een achtergrondthread. Daarnaast maakt de onderliggende task gebruik van een impliciete annuleringstoken. Als u daarentegen een task object retourneert, wordt de werkfunctie synchroon uitgevoerd. Als u daarom een task object retourneert, moet u ervoor zorgen dat eventuele langdurige bewerkingen in uw werkfunctie ook worden uitgevoerd als taken om ervoor te zorgen dat uw app responsief blijft. Bovendien gebruikt de onderliggende task geen impliciet annulatietoken. Daarom moet u uw werkfunctie definiëren om een cancellation_token object te verwerken als u ondersteuning voor annulering nodig hebt wanneer u een task object retourneert, van create_async.

In het volgende voorbeeld ziet u de verschillende manieren om een IAsyncAction object te maken dat kan worden gebruikt door een ander Windows Runtime-onderdeel.

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

Voorbeeld: een C++ Windows Runtime-component maken en gebruiken vanuit een C# client

Overweeg een app die gebruikmaakt van XAML en C# om de gebruikersinterface en een C++ Windows Runtime-onderdeel te definiëren om rekenintensieve bewerkingen uit te voeren. In dit voorbeeld berekent het C++-onderdeel welke getallen in een bepaald bereik priem zijn. Als u de verschillen wilt illustreren tussen de vier asynchrone taakinterfaces van Windows Runtime, start u in Visual Studio door een lege oplossing te maken en deze Primeseen naam te geven. Voeg vervolgens een Windows Runtime Component-project toe aan de oplossing en noem het PrimesLibrary. Voeg de volgende code toe aan het gegenereerde C++-headerbestand (in dit voorbeeld wordt class1.h gewijzigd in Primes.h). Elke public methode definieert een van de vier asynchrone interfaces. De methoden die een waarde retourneren, retourneren een Windows::Foundation::Collections::IVector<int-object> . De methoden die voortgang rapporteren, produceren double waarden die het percentage van het totale werk definiëren dat is voltooid.

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

Opmerking

Standaard eindigen asynchrone methodenamen in Windows Runtime meestal op 'Async'.

Voeg de volgende code toe aan het gegenereerde C++-bronbestand (in dit voorbeeld wordt de naam van Class1.cpp gewijzigd in Primes.cpp). De is_prime functie bepaalt of de invoer prime is. De resterende methoden implementeren de Primes klasse. Elke aanroep van create_async gebruikt een methodehandtekening die compatibel is met de methode waaruit deze wordt aangeroepen. Omdat Primes::ComputePrimesAsyncIAsyncAction retourneert, retourneert de aan create_async opgegeven werkfunctie geen waarde en neemt het geen progress_reporter-object als parameter.

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

Elke methode voert eerst validatie uit om ervoor te zorgen dat de invoerparameters niet-negatief zijn. Als een invoerwaarde negatief is, genereert de methode Platform::InvalidArgumentException. Foutafhandeling wordt verderop in deze sectie uitgelegd.

Als u deze methoden wilt gebruiken vanuit een UWP-app, gebruikt u de sjabloon Visual C# Blank App (XAML) om een tweede project toe te voegen aan de Visual Studio-oplossing. In dit voorbeeld wordt het project Primesgenoemd. Voeg vervolgens vanuit het Primes project een verwijzing naar het PrimesLibrary project toe.

Voeg de volgende code toe aan MainPage.xaml. Deze code definieert de gebruikersinterface, zodat u het C++-onderdeel kunt aanroepen en resultaten kunt weergeven.

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

Voeg de volgende code toe aan de MainPage klasse in MainPage.xaml. Deze code definieert een Primes object en de gebeurtenis-handlers van de knop.

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

Deze methoden gebruiken de async en await trefwoorden om de gebruikersinterface bij te werken nadat de asynchrone bewerkingen zijn voltooid. Zie Threading en asynchrone programmering voor informatie over asynchrone codering in UWP-apps.

De getPrimesCancellation en cancelGetPrimes methoden werken samen om de gebruiker in staat te stellen de bewerking te annuleren. Wanneer de gebruiker de knop Annuleren kiest, roept de cancelGetPrimes methode IAsyncOperationWithProgress<TResult, TProgress>::Cancel aan om de bewerking te annuleren. De Gelijktijdigheidsruntime, die de onderliggende asynchrone bewerking beheert, genereert een intern uitzonderingstype dat door de Windows Runtime wordt opgevangen, waarmee wordt gecommuniceerd dat de annulering is voltooid. Zie Annulering voor meer informatie over het annuleringsmodel.

Belangrijk

Als u wilt dat de PPL correct rapporteert aan de Windows Runtime dat de bewerking is geannuleerd, ondervang het interne exceptietype niet. Dit betekent dat u ook niet alle uitzonderingen hoeft te ondervangen (catch (...)). Als u alle uitzonderingen moet opvangen, gooit u de uitzondering opnieuw om ervoor te zorgen dat de Windows Runtime de annuleringsbewerking kan voltooien.

In de volgende afbeelding ziet u de Primes app nadat elke optie is gekozen.

Windows Runtime Primes-app.

Voor een voorbeeld van het gebruik van create_async om asynchrone taken te maken die door andere talen kunnen worden benut, zie Het gebruik van C++ in het Bing Maps Trip Optimizer voorbeeld.

Het beheren van de uitvoeringsthread

Windows Runtime maakt gebruik van het COM-threadingmodel. In dit model worden objecten gehost in verschillende appartementen, afhankelijk van hoe ze hun synchronisatie verwerken. Thread-veilige objecten worden gehost in het Multi-Threaded Apartment (MTA). Objecten die toegankelijk moeten zijn voor één thread, worden gehost in een appartement met één thread (STA).

In een app met een gebruikersinterface is de ASTA-thread (Application STA) verantwoordelijk voor het pompen van vensterberichten en is dit de enige thread in het proces waarmee de door STA gehoste UI-besturingselementen kunnen worden bijgewerkt. Dit heeft twee gevolgen. Om ervoor te zorgen dat de app responsief blijft, mogen alle CPU-intensieve en I/O-bewerkingen niet worden uitgevoerd op de ASTA-thread. Ten tweede moeten de resultaten die afkomstig zijn van achtergrondthreads, worden teruggezet naar de ASTA om de gebruikersinterface bij te werken. In een C++ UWP-app worden MainPage en alle andere XAML-pagina's allemaal uitgevoerd op de ATSA. Daarom worden taakvoortzettingen die op de ASTA zijn gedeclareerd, daar standaard uitgevoerd, zodat u besturingselementen rechtstreeks in het vervolgproces kunt bijwerken. Als u echter een taak in een andere taak nestelt, worden eventuele voortzettingen van die geneste taak uitgevoerd in de MTA. Daarom moet u overwegen of u expliciet wilt opgeven in welke context deze voortzettingen worden uitgevoerd.

Een taak die is gemaakt op basis van een asynchrone bewerking, zoals IAsyncOperation<TResult>, maakt gebruik van speciale semantiek waarmee u de details van de threading kunt negeren. Hoewel een bewerking kan worden uitgevoerd op een achtergrondthread (of het kan helemaal niet worden ondersteund door een thread), worden de vervolgbewerkingen standaard gegarandeerd uitgevoerd op het appartement dat de vervolgbewerkingen heeft gestart (met andere woorden, van het appartement dat werd aangeroepen task::then). U kunt de concurrency::task_continuation_context klasse gebruiken om de uitvoeringscontext van een vervolgtaak te controleren. Gebruik deze statische helpermethoden om objecten te maken task_continuation_context :

U kunt een task_continuation_context-object doorgeven aan de task::then-methode om de uitvoeringscontext van de voortzetting expliciet te controleren, of u kunt de taak naar een andere omgeving overbrengen en vervolgens de task::then-methode aanroepen om de uitvoeringscontext impliciet te controleren.

Belangrijk

Omdat de belangrijkste UI-thread van UWP-apps wordt uitgevoerd onder STA, worden vervolgen die u op die STA maakt standaard uitgevoerd op de STA. Doorvoeringen die u aanmaakt op de MTA worden op de MTA uitgevoerd.

In de volgende sectie ziet u een app die een bestand van de schijf leest, de meest voorkomende woorden in dat bestand vindt en vervolgens de resultaten in de gebruikersinterface weergeeft. De laatste bewerking, waarbij de gebruikersinterface wordt bijgewerkt, vindt plaats op de UI-thread.

Belangrijk

Dit gedrag is specifiek voor UWP-apps. Voor desktop-applicaties heeft u geen controle over waar de voortzettingen worden uitgevoerd. In plaats daarvan kiest de planner een werkrolthread waarop elke voortzetting moet worden uitgevoerd.

Belangrijk

Roep concurrency::task::wait niet aan in de body van een continuatie dat op de STA draait. Anders genereert de runtime gelijktijdigheid::invalid_operation omdat deze methode de huidige thread blokkeert en ervoor kan zorgen dat de app niet meer reageert. U kunt echter de gelijktijdigheid::taak::get-methode aanroepen om het resultaat van de antecedent-taak te ontvangen in een vervolg op basis van een taak.

Voorbeeld: Uitvoering beheren in een Windows Runtime-app met C++ en XAML

Overweeg een C++ XAML-app die een bestand van de schijf leest, de meest voorkomende woorden in dat bestand vindt en vervolgens de resultaten in de gebruikersinterface weergeeft. Als u deze app wilt maken, start u in Visual Studio door een leeg app-project (Universal Windows) te maken en deze CommonWordseen naam te geven. Geef in het app-manifest de mogelijkheid documentenbibliotheek op om de app toegang te geven tot de map Documenten. Voeg ook het bestandstype Text (.txt) toe aan de declaratiesectie van het app-manifest. Zie Pakketten, implementatie en query's van Windows-apps voor meer informatie over app-mogelijkheden en -verklaringen.

Werk het Grid element in MainPage.xaml bij om een ProgressRing element en een TextBlock element op te nemen. De ProgressRing geeft aan dat de bewerking wordt uitgevoerd en de TextBlock toont de resultaten van de berekening.

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

Voeg de volgende #include instructies toe aan pch.h.

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

Voeg de volgende methodedeclaraties toe aan de MainPage klasse (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);

Voeg de volgende using instructies toe aan MainPage.cpp.

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

Implementeer in MainPage.cpp de MainPage::MakeWordList, MainPage::FindCommonWordsen MainPage::ShowResults methoden. De MainPage::MakeWordList en MainPage::FindCommonWords voeren rekenintensieve bewerkingen uit. De MainPage::ShowResults methode geeft het resultaat weer van de berekening in de gebruikersinterface.

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

Wijzig de MainPage constructor om een keten van vervolgtaken te maken die in de gebruikersinterface worden weergegeven met de algemene woorden in het boek The Iliad by Homer. De eerste twee vervolgtaken, die de tekst opsplitsen in afzonderlijke woorden en veelvoorkomende woorden zoeken, kunnen tijdrovend zijn en daarom expliciet op de achtergrond worden uitgevoerd. De uiteindelijke vervolgtaak, die de gebruikersinterface bijwerkt, geeft geen vervolgcontext op en volgt daarom de regels voor het threaden van appartementen.

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

Opmerking

In dit voorbeeld ziet u hoe u uitvoeringscontexten opgeeft en hoe u een keten van vervolgbewerkingen opstelt. Ter herinnering, standaard voert een taak die is gemaakt op basis van een asynchrone bewerking zijn vervolgbewerkingen uit op het appartement dat de oproep heeft gedaan task::then. Daarom wordt in dit voorbeeld gebruikt task_continuation_context::use_arbitrary om op te geven dat bewerkingen die niet betrekking hebben op de gebruikersinterface, worden uitgevoerd op een achtergrondthread.

In de volgende afbeelding ziet u de resultaten van de CommonWords app.

Windows Runtime CommonWords-app.

In dit voorbeeld is het mogelijk om annulering te ondersteunen omdat de task objecten die ondersteuning bieden create_async een impliciet annuleringstoken gebruiken. Definieer uw werkfunctie om een cancellation_token object te nemen als uw taken op een coöperatieve manier op annulering moeten reageren. Zie Annulering in de PPL voor meer informatie over annulering in de PPL

Zie ook

Gelijktijdigheidsruntime