Dela via


Skapa asynkrona åtgärder i C++ för UWP-appar

Det här dokumentet beskriver några av de viktigaste punkterna att tänka på när du använder aktivitetsklassen för att skapa Windows ThreadPool-baserade asynkrona åtgärder i en UWP-app (Universal Windows Runtime).

Användningen av asynkron programmering är en nyckelkomponent i Windows Runtime-appmodellen eftersom den gör det möjligt för appar att förbli dynamiska för användarindata. Du kan starta en tidskrävande uppgift utan att blockera användargränssnittstråden och du kan få resultatet av aktiviteten senare. Du kan också avbryta aktiviteter och ta emot förloppsmeddelanden när aktiviteter körs i bakgrunden. Dokumentet Asynkron programmering i C++ ger en översikt över det asynkrona mönster som är tillgängligt i Visual C++ för att skapa UWP-appar. Det här dokumentet lär dig hur du både använder och skapar kedjor av asynkrona Windows Runtime-åtgärder. Det här avsnittet beskriver hur du använder typerna i ppltasks.h för att skapa asynkrona åtgärder som kan användas av en annan Windows Runtime-komponent och hur du styr hur asynkront arbete körs. Överväg också att läsa Async-programmeringsmönster och tips i Hilo (Windows Store-appar med C++ och XAML) för att lära oss hur vi använde aktivitetsklassen för att implementera asynkrona åtgärder i Hilo, en Windows Runtime-app med C++ och XAML.

Anmärkning

Du kan använda biblioteket parallella mönster (PPL) och Asynkrona agenter i en UWP-app. Du kan emellertid inte använda Schemaläggaren eller Resurshanteraren. Det här dokumentet beskriver ytterligare funktioner som PPL tillhandahåller som endast är tillgängliga för en UWP-app och inte för en skrivbordsapp.

Huvudpunkter

  • Använd samtidighet::create_async för att skapa asynkrona åtgärder som kan användas av andra komponenter (som kan skrivas på andra språk än C++).

  • Använd concurrency::progress_reporter för att rapportera förloppsmeddelanden till komponenter som anropar dina asynkrona åtgärder.

  • Använd annulleringstoken för att möjliggöra avbrytande av interna asynkrona operationer.

  • Funktionens create_async beteende beror på returtypen för den arbetsfunktion som skickas till den. En arbetsfunktion som returnerar en aktivitet (antingen task<T> eller task<void>) körs synkront i kontexten som heter create_async. En arbetsfunktion som returnerar T eller void körs i en godtycklig kontext.

  • Du kan använda metoden concurrency::task::then för att skapa en kedja med aktiviteter som körs en efter en. I en UWP-app beror standardkontexten för en aktivitets fortsättning på hur aktiviteten konstruerades. Om uppgiften skapades genom att skicka en asynkron åtgärd till aktivitetskonstruktorn, eller genom att skicka ett lambda-uttryck som returnerar en asynkron åtgärd, är standardkontexten för alla fortsättningar av aktiviteten den aktuella kontexten. Om aktiviteten inte har konstruerats från en asynkron åtgärd används en godtycklig kontext som standard för aktivitetens fortsättningar. Du kan åsidosätta standardkontexten med klassen concurrency::task_continuation_context .

I det här dokumentet

Skapa asynkrona åtgärder

Du kan använda uppgifts- och fortsättningsmodellen i PPL (Parallel Patterns Library) för att definiera bakgrundsaktiviteter samt ytterligare aktiviteter som körs när föregående uppgift slutförs. Den här funktionen tillhandahålls av concurrency::task klassen. Mer information om den här modellen och klassen finns i taskAktivitetsparallellitet.

Windows Runtime är ett programmeringsgränssnitt som du kan använda för att skapa UWP-appar som endast körs i en speciell operativsystemmiljö. Sådana appar använder auktoriserade funktioner, datatyper och enheter och distribueras från Microsoft Store. Windows Runtime representeras av ABI ( Application Binary Interface ). ABI är ett underliggande binärt kontrakt som gör Windows Runtime-API:er tillgängliga för programmeringsspråk som Visual C++.

Genom att använda Windows Runtime kan du använda de bästa funktionerna i olika programmeringsspråk och kombinera dem till en app. Du kan till exempel skapa ditt användargränssnitt i JavaScript och utföra den beräkningsintensiva applogik i en C++-komponent. Möjligheten att utföra dessa beräkningsintensiva åtgärder i bakgrunden är en viktig faktor för att hålla användargränssnittet responsivt. task Eftersom klassen är specifik för C++, måste du använda ett Windows Runtime-gränssnitt för att kommunicera asynkrona åtgärder till andra komponenter (som kan skrivas på andra språk än C++). Windows Runtime innehåller fyra gränssnitt som du kan använda för att representera asynkrona åtgärder:

Windows::Foundation::IAsyncAction
Representerar en asynkron åtgärd.

Windows::Foundation::IAsyncActionWithProgress<TProgress>
Representerar en asynkron åtgärd som rapporterar förlopp.

Windows::Foundation::IAsyncOperation<TResult>
Representerar en asynkron åtgärd som returnerar ett resultat.

Windows::Foundation::IAsyncOperationWithProgress<TResult, TProgress>
Representerar en asynkron åtgärd som returnerar ett resultat och rapporterar förlopp.

Begreppet åtgärd innebär att den asynkrona aktiviteten inte genererar något värde (tänk på en funktion som returnerar void). Begreppet åtgärd innebär att den asynkrona aktiviteten genererar ett värde. Begreppet förlopp innebär att uppgiften kan rapportera förloppsmeddelanden till anroparen. JavaScript, .NET Framework och Visual C++ ger var och en sitt eget sätt att skapa instanser av dessa gränssnitt för användning över ABI-gränsen. För Visual C++tillhandahåller PPL funktionen concurrency::create_async . Den här funktionen skapar en asynkron åtgärd eller åtgärd i Windows Runtime som representerar slutförandet av en uppgift. Funktionen create_async tar en arbetsfunktion (vanligtvis ett lambda-uttryck), skapar internt ett task objekt och omsluter uppgiften i ett av de fyra asynkrona Windows Runtime-gränssnitten.

Anmärkning

Använd create_async endast när du behöver skapa funktioner som kan nås från ett annat språk eller en annan Windows Runtime-komponent. task Använd klassen direkt när du vet att åtgärden både produceras och används av C++-kod i samma komponent.

Returtypen create_async bestäms av typen av argument. Om din arbetsfunktion till exempel inte returnerar ett värde och inte rapporterar förlopp returnerar create_asyncIAsyncAction. Om din arbetsfunktion inte returnerar ett värde och även rapporterar förloppet, kommer create_async att returnera IAsyncActionWithProgress. Om du vill rapportera förloppet anger du ett concurrency::p rogress_reporter-objekt som parameter för din arbetsfunktion. Med möjligheten att rapportera förlopp kan du rapportera hur mycket arbete som utfördes och hur mycket som återstår (till exempel i procent). Du kan också rapportera resultat när de blir tillgängliga.

Gränssnitten IAsyncAction, IAsyncActionWithProgress<TProgress>, IAsyncOperation<TResult> och IAsyncActionOperationWithProgress<TProgress, TProgress> tillhandahåller varsin Cancel-metod som gör att du kan avbryta den asynkrona åtgärden. Klassen task fungerar med annulleringstoken. När du använder en annulleringstoken för att avbryta arbetet startar inte körningen nytt arbete som prenumererar på den token. Arbete som redan är aktivt kan övervaka dess annulleringstoken och stoppa när det kan. Den här mekanismen beskrivs mer detaljerat i dokumentet Annullering i PPL. Du kan koppla avbrytande av aktivitet med Windows Runtime-metoderna Cancel på två sätt. Först kan du definiera den arbetsfunktion som du skickar till create_async så att den tar ett concurrency::cancellation_token-objekt. Cancel När metoden anropas avbryts den här annulleringstoken och de normala annulleringsreglerna gäller för det underliggande task objektet som stöder anropetcreate_async. Om du inte anger något cancellation_token objekt definierar det underliggande task objektet ett implicit. Definiera ett cancellation_token objekt när du behöver samarbeta för att svara på annullering i din arbetsfunktion. Avsnittet Exempel: Kontrollera körning i en Windows Runtime-app med C++ och XAML visar ett exempel på hur du utför annullering i en UWP-app (Universal Windows Platform) med C# och XAML som använder en anpassad Windows Runtime C++-komponent.

Varning

I en kedja av aktivitetsfortsättningar rensar du alltid tillståndet och anropar sedan samtidighet::cancel_current_task när annulleringstoken avbryts. Om du kommer tillbaka tidigt i stället för att anropa cancel_current_taskövergår åtgärden till det slutförda tillståndet i stället för det avbrutna tillståndet.

I följande tabell sammanfattas de kombinationer som du kan använda för att definiera asynkrona åtgärder i din app.

Skapa det här Windows Runtime-gränssnittet Returnera den här typen från create_async Skicka dessa parametertyper till din arbetsfunktion för att använda en implicit annulleringstoken Skicka dessa parametertyper till din arbetsfunktion för att använda en explicit annulleringstoken
IAsyncAction void eller task<void> (ingen) (cancellation_token)
IAsyncActionWithProgress<TProgress> void eller task<void> (progress_reporter) (progress_reporter, cancellation_token)
IAsyncOperation<TResult> T eller task<T> (ingen) (cancellation_token)
IAsyncActionOperationWithProgress<TProgress, TProgress> T eller task<T> (progress_reporter) (progress_reporter, cancellation_token)

Du kan returnera ett värde eller ett task objekt från den arbetsfunktion som du skickar till create_async funktionen. Dessa varianter ger olika beteenden. När du returnerar ett värde omsluts arbetsfunktionen i en task så att den kan köras på en bakgrundstråd. Dessutom använder den underliggande funktionen task en implicit annulleringstoken. Om du returnerar ett task objekt körs arbetsfunktionen synkront. Om du returnerar ett task objekt kontrollerar du därför att alla långa åtgärder i din arbetsfunktion också körs som uppgifter så att appen kan förbli dynamisk. Dessutom använder den underliggande task inte en implicit annulleringstoken. Därför måste du definiera din arbetsfunktion för att ta ett cancellation_token objekt om du behöver stöd för annullering när du returnerar ett task objekt från create_async.

I följande exempel visas de olika sätten att skapa ett IAsyncAction objekt som kan användas av en annan Windows Runtime-komponent.

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

Exempel: Skapa en C++ Windows Runtime-komponent och använda den från C#

Överväg en app som använder XAML och C# för att definiera användargränssnittet och en C++ Windows Runtime-komponent för att utföra beräkningsintensiva åtgärder. I det här exemplet beräknar C++-komponenten vilka tal i ett visst intervall som är primära. För att illustrera skillnaderna mellan de fyra asynkrona Windows Runtime-aktivitetsgränssnitten börjar du i Visual Studio genom att skapa en tom lösning och namnge den Primes. Lägg sedan till ett Windows Runtime-komponentprojekt i lösningen och namnge det PrimesLibrary. Lägg till följande kod i den genererade C++-huvudfilen (det här exemplet byter namn på Class1.h till Primes.h). Varje public metod definierar ett av de fyra asynkrona gränssnitten. Metoderna som returnerar ett värde returnerar ett Windows::Foundation::Collections::IVector<int-objekt> . Metoderna som rapporterar förlopp genererar double värden som definierar procentandelen av det totala arbete som har slutförts.

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

Anmärkning

Enligt konventionen slutar asynkrona metodnamn i Windows Runtime vanligtvis med "Async".

Lägg till följande kod i den genererade C++-källfilen (det här exemplet byter namn på Class1.cpp till Primes.cpp). Funktionen is_prime avgör om dess indata är primära. De återstående metoderna implementerar Primes klassen. Varje anrop till create_async använder en signatur som är kompatibel med metoden som den anropas från. Eftersom Primes::ComputePrimesAsync returnerar IAsyncAction, returnerar inte den arbetsfunktion som tillhandahålls till create_async ett värde och tar inte ett progress_reporter-objekt som 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;
    });
}

Varje metod utför först validering för att säkerställa att indataparametrarna inte är negativa. Om ett indatavärde är negativt genererar metoden Platform::InvalidArgumentException. Felhantering förklaras senare i det här avsnittet.

Om du vill använda dessa metoder från en UWP-app använder du XAML-mallen (Visual C# Blank App) för att lägga till ett andra projekt i Visual Studio-lösningen. I det här exemplet namnges projektet Primes. Lägg sedan till en referens till Primes projektet från PrimesLibrary projektet.

Lägg till följande kod i MainPage.xaml. Den här koden definierar användargränssnittet så att du kan anropa C++-komponenten och visa resultat.

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

Lägg till följande kod i MainPage klassen i MainPage.xaml. Den här koden definierar ett Primes objekt och knapphändelsehanterare.

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

Dessa metoder använder nyckelorden async och await för att uppdatera användargränssnittet när de asynkrona åtgärderna har slutförts. Information om asynkron kodning i UWP-appar finns i Trådning och asynkron programmering.

Metoderna getPrimesCancellation och cancelGetPrimes fungerar tillsammans så att användaren kan avbryta åtgärden. När användaren väljer knappen cancelGetPrimes anropar metoden IAsyncOperationWithProgress<TResult, TProgress>::Avbryt för att avbryta åtgärden. Concurrency Runtime, som hanterar den underliggande asynkrona åtgärden, genererar en intern undantagstyp som fångas av Windows Runtime för att meddela att annulleringen har slutförts. Mer information om annulleringsmodellen finns i Annullering.

Viktigt!

Om du vill att PPL ska kunna rapportera till Windows Runtime att åtgärden har avbrutits ska du inte fånga upp den här interna undantagstypen. Det innebär att du inte heller bör fånga alla undantag (catch (...)). Om du måste fånga alla undantag, kasta om undantaget för att säkerställa att Windows Runtime kan slutföra annulleringsåtgärden.

Följande bild visar Primes appen när varje alternativ har valts.

Windows Runtime Primes-app.

Ett exempel som använder create_async för att skapa asynkrona uppgifter som kan användas av andra språk finns i Använda C++ i exemplet med reseoptimeraren i Bing Maps.

Kontrollera exekveringstråden

Windows Runtime använder COM-trådningsmodellen. I den här modellen finns objekt i olika lägenheter, beroende på hur de hanterar synkroniseringen. Trådsäkra objekt finns i den flertrådade lägenheten (MTA). Objekt som måste nås av en enda tråd är placerade i en single-threaded apartment (STA).

I en app som har ett användargränssnitt ansvarar ASTA-tråden (Application STA) för att pumpa fönstermeddelanden och är den enda tråden i processen som kan uppdatera de STA-värdbaserade användargränssnittskontrollerna. Detta får två konsekvenser. För att göra det möjligt för appen att förbli dynamisk bör alla CPU-intensiva åtgärder och I/O-åtgärder inte köras på ASTA-tråden. För det andra måste resultat som kommer från bakgrundstrådar konverteras tillbaka till ASTA för att uppdatera användargränssnittet. I en C++ UWP-app körs MainPage och andra XAML-sidor alla på ATSA. Därför körs aktivitetsfortsättningar som deklareras i ASTA där som standard, så att du kan uppdatera kontrollerna direkt i fortsättningskroppen. Men om du nästlar en uppgift i en annan uppgift körs eventuella fortsättningar av den nästlade uppgiften i MTA. Därför måste du överväga om du uttryckligen ska ange i vilken kontext dessa fortsättningar körs.

En uppgift som skapas från en asynkron åtgärd, till exempel IAsyncOperation<TResult>, använder särskilda semantik som kan hjälpa dig att ignorera trådinformationen. Även om en åtgärd kan köras på en bakgrundstråd (eller så kanske den inte backas upp av en tråd alls), garanteras dess fortsättningar som standard att köras på den lägenhet som startade fortsättningsåtgärderna (med andra ord från lägenheten som heter task::then). Du kan använda klassen concurrency::task_continuation_context för att styra körningskontexten för en fortsättning. Använd dessa statiska hjälpmetoder för att skapa task_continuation_context objekt:

Du kan skicka ett task_continuation_context objekt till uppgiften::sedan metod för att uttryckligen styra körningskontexten för fortsättningen eller så kan du skicka uppgiften till en annan lägenhet och sedan anropa task::then metoden för att implicit kontrollera körningskontexten.

Viktigt!

Eftersom den huvudsakliga användargränssnittstråden för UWP-appar körs under STA, kommer fortsättningar som du skapar på den STA att som standard köras på STA. Fortsättningar som du skapar på MTA:n körs på MTA.

I följande avsnitt visas en app som läser en fil från disken, hittar de vanligaste orden i filen och sedan visar resultatet i användargränssnittet. Den sista åtgärden, som uppdaterar användargränssnittet, sker i användargränssnittstråden.

Viktigt!

Det här beteendet är specifikt för UWP-appar. För skrivbordsappar styr du inte var fortsättningar körs. I stället väljer schemaläggaren en arbetstråd där varje fortsättning ska köras.

Viktigt!

Anropa inte samtidighet::uppgift::vänta i brödtexten i en fortsättning som körs på STA. Annars genererar körningen samtidighet::invalid_operation eftersom den här metoden blockerar den aktuella tråden och kan leda till att appen inte svarar. Du kan dock anropa metoden concurrency::task::get för att ta emot resultatet av den tidigare aktiviteten i en aktivitetsbaserad fortsättning.

Exempel: Kontrollera körning i en Windows Runtime-app med C++ och XAML

Överväg att använda en C++ XAML-app som läser en fil från disken, hittar de vanligaste orden i filen och sedan visar resultatet i användargränssnittet. Om du vill skapa den här appen börjar du i Visual Studio genom att skapa ett tomt appprojekt (Universal Windows) och namnge det CommonWords. I appmanifestet anger du funktionen Dokumentbibliotek för att göra det möjligt för appen att komma åt mappen Dokument. Lägg också till filtypen Text (.txt) i deklarationsavsnittet i appmanifestet. Mer information om appfunktioner och -deklarationer finns i Paketering, distribution och fråga om Windows-appar.

Uppdatera elementet Grid i MainPage.xaml så att det innehåller ett ProgressRing element och ett TextBlock element. ProgressRing Anger att åtgärden pågår och TextBlock visar resultatet av beräkningen.

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

Lägg till följande #include instruktioner i pch.h.

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

Lägg till följande metoddeklarationer i MainPage klassen (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);

Lägg till följande using instruktioner i MainPage.cpp.

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

I MainPage.cpp implementerar du metoderna MainPage::MakeWordList, MainPage::FindCommonWords, och MainPage::ShowResults. Och MainPage::MakeWordListMainPage::FindCommonWords utför beräkningsintensiva åtgärder. Metoden MainPage::ShowResults visar resultatet av beräkningen i användargränssnittet.

// Splits the provided text string into individual words.
task<vector<wstring>> MainPage::MakeWordList(String^ text)
{
    return create_task([text]() -> vector<wstring>
    {
        vector<wstring> words;

        // Add continuous sequences of alphanumeric characters to the string vector.
        wstring current_word;
        for (wchar_t ch : text)
        {
            if (!iswalnum(ch))
            {
                if (current_word.length() > 0)
                {
                    words.push_back(current_word);
                    current_word.clear();
                }
            }
            else
            {
                current_word += ch;
            }
        }

        return words;
    });
}

// Finds the most common words that are at least the provided minimum length.
task<vector<pair<wstring, size_t>>> MainPage::FindCommonWords(const vector<wstring>& words, size_t min_length, size_t count)
{
    return create_task([words, min_length, count]() -> vector<pair<wstring, size_t>>
    {
        typedef pair<wstring, size_t> pair;

        // Counts the occurrences of each word.
        concurrent_unordered_map<wstring, size_t> counts;

        parallel_for_each(begin(words), end(words), [&counts, min_length](const wstring& word)
        {
            // Increment the count of words that are at least the minimum length. 
            if (word.length() >= min_length)
            {
                // Increment the count.
                InterlockedIncrement(&counts[word]);
            }
        });

        // Copy the contents of the map to a vector and sort the vector by the number of occurrences of each word.
        vector<pair> wordvector;
        copy(begin(counts), end(counts), back_inserter(wordvector));

        sort(begin(wordvector), end(wordvector), [](const pair& x, const pair& y)
        {
            return x.second > y.second;
        });

        size_t size = min(wordvector.size(), count);
        wordvector.erase(begin(wordvector) + size, end(wordvector));

        return wordvector;
    });
}

// Shows the most common words on the UI. 
void MainPage::ShowResults(const vector<pair<wstring, size_t>>& commonWords)
{
    wstringstream ss;
    ss << "The most common words that have five or more letters are:";
    for (auto commonWord : commonWords)
    {
        ss << endl << commonWord.first << L" (" << commonWord.second << L')';
    }

    // Update the UI.
    Results->Text = ref new String(ss.str().c_str());
}

MainPage Ändra konstruktorn för att skapa en kedja av fortsättningsuppgifter som i användargränssnittet visar de vanliga orden i boken The Iliad by Homer. De två första fortsättningsuppgifterna, som delar upp texten i enskilda ord och hittar vanliga ord, kan vara tidskrävande och är därför uttryckligen inställda på att köras i bakgrunden. Den sista fortsättningsaktiviteten, som uppdaterar användargränssnittet, anger ingen fortsättningskontext och följer därför reglerna för lägenhetstrådning.

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

Anmärkning

Det här exemplet visar hur du anger körningskontexter och hur du skapar en fortsättningskedja. Kom ihåg att som standard kör en aktivitet som har skapats från en asynkron åtgärd sina fortsättningar på lägenheten som heter task::then. Därför används task_continuation_context::use_arbitrary det här exemplet för att ange att åtgärder som inte omfattar användargränssnittet ska utföras på en bakgrundstråd.

Följande bild visar resultatet av CommonWords appen.

Windows Runtime CommonWords-app.

I det här exemplet är det möjligt att stödja annullering eftersom de task objekt som stöder create_async använder en implicit annulleringstoken. Definiera din arbetsfunktion för att ta ett cancellation_token objekt om dina uppgifter behöver svara på annullering på ett samverkande sätt. Mer information om annullering i PPL finns i Annullering i PPL

Se även

Samtidighetskörning