Freigeben über


Erstellen von asynchronen Vorgängen in C++ für Windows Store-Apps

In diesem Dokument werden einige der wichtigsten zu berücksichtigenden Punkte für das Verwenden der Concurrency Runtime für asynchrone Vorgänge in einer Windows Store-App beschrieben.

Bei der asynchronen Programmierung handelt es sich um eine Schlüsselkomponente des Windows Store-App-Modells, da Apps auf diese Weise stets auf Benutzereingaben reaktionsfähig bleiben. Sie können eine lang dauernde Aufgabe starten, ohne den Benutzeroberflächenthread zu blockieren, und die Ergebnisse der Aufgabe später empfangen. Sie können Aufgaben auch abbrechen und Statusbenachrichtigungen beim Ausführen von Aufgaben im Hintergrund erhalten. Im Dokument Asynchrone Programmierung in C++ finden Sie eine Übersicht der asynchronen Muster, die in Visual C++ zum Erstellen von Windows Store-Apps zur Verfügung stehen. In diesem Dokument wird sowohl das Nutzen als auch das Erstellen von Ketten asynchroner Windows-Runtime-Vorgänge erläutert. In diesem Abschnitt wird beschrieben, wie mit der Concurrency Runtime von einer anderen Windows-Runtime-Komponente nutzbare asynchrone Vorgänge erstellt und wie die Ausführung asynchroner Arbeit gesteuert werden kann. Außerdem können Sie sich unter Muster und Tipps für die asynchrone Programmierung in Hilo (Windows Store-Apps mit C++ und XAML)darüber informieren, wie mit der Concurrency Runtime asynchrone Vorgänge in Hilo implementiert wurden, einer auf C++ und XAML beruhenden Windows Store-App.

Hinweis

In einer Windows Store-App können die Parallel Patterns Library (PPL) und die Asynchronous Agents Library vrwendet werden.Aufgabenplaner oder Ressourcen-Manager können jedoch nicht verwendet werden.In diesem Dokument werden zusätzliche von der Concurrency Runtime bereitgestellte Funktionen beschrieben, die nur für Windows Store- und nicht für Desktop-Apps verfügbar sind.

Wesentliche Punkte

  • Erstellen Sie mithilfe von concurrency::create_async asynchrone Vorgänge, die von anderen Komponenten genutzt werden können (die möglicherweise in einer anderen Sprache als C++ geschrieben sind).

  • Verwenden Sie concurrency::progress_reporter zum Übermitteln von Statusbenachrichtigungen an Komponenten, von denen die asynchronen Vorgänge aufgerufen werden.

  • Mithilfe von Abbruchtoken können interne asynchrone Vorgänge abgebrochen werden.

  • Das Verhalten der create_async-Funktion hängt vom Rückgabetyp der daran übergebenen Arbeitsfunktion ab. Eine Arbeitsfunktion, die eine Aufgabe zurückgibt (entweder task<T> oder task<void>) oder synchron in dem Kontext ausgeführt wird, in dem create_async aufgerufen wurde. Eine Arbeitsfunktion, die T oder void zurückgibt, wird in einem die oft ausgegebene Befehlszeilen Kontext ausgeführt.

  • Mithilfe der concurrency::task::then-Methode können Sie eine Kette von Aufgaben erstellen, die nacheinander ausgeführt werden. In einer Windows Store-App hängt der Standardkontext für die Fortsetzungen einer Aufgabe davon ab, wie diese Aufgabe erstellt wurde. Wenn die Aufgabe durch Übergabe einer asynchronen Aktion an den Aufgabenkonstruktor oder durch Übergabe eines Lambda-Ausdrucks erstellt wurde, der eine asynchrone Aktion zurückgibt, handelt es sich beim Standardkontext für alle Fortsetzungen dieser Aufgabe um den aktuellen Kontext. Wenn die Aufgabe nicht durch eine asynchrone Aktion erstellt wird, wird für die Fortsetzungen der Aufgabe standardmäßig ein beliebiger Kontext verwendet. Sie können den Standardkontext mit der concurrency::task_continuation_context-Klasse überschreiben.

Inhalt dieses Dokuments

  • Erstellen von asynchronen Operationen

  • Beispiel: Erstellen einer C++-Komponente für Windows-Runtime

  • Steuern des Ausführungs-Threads

  • Beispiel: Steuern der Ausführung in einer Windows Store-App mit C++ und XAML

Erstellen von asynchronen Operationen

Sie können das Aufgaben- und Fortsetzungsmodell in der Parallel Patterns Library (PPL) verwenden, um Hintergrundaufgaben sowie zusätzliche Aufgaben zu definieren, die nach Abschluss der vorherigen Aufgabe ausgeführt werden sollen. Diese Funktionalität wird von der concurrency::task-Klasse bereitgestellt. Weitere Informationen zu diesem Modell und der task-Klasse finden Sie unter Aufgabenparallelität (Concurrency Runtime).

Mit der Windows-Runtime-Programmierschnittstelle können Sie Windows Store-Apps erstellen, die nur in einer bestimmten Betriebssystemumgebung ausgeführt werden. Solche Apps verwenden autorisierte Funktionen, Datentypen und Geräte und werden über den Windows Store vertrieben. Windows-Runtime wird durch die ABI (Application Binary Interface) dargestellt. Die ABI ist ein zugrunde liegender binärer Vertrag, der Windows-Runtime-APIs für Programmiersprachen wie Visual C++ verfügbar macht.

Mithilfe von Windows-Runtime können Sie die besten Funktionen verschiedener Programmiersprachen verwenden und in einer App kombinieren. Beispielsweise können Sie die Benutzeroberfläche in JavaScript erstellen und die rechenintensive App-Logik in einer C++-Komponente ausführen. Die Fähigkeit, diese rechenintensiven Vorgänge im Hintergrund auszuführen, ist ein Schlüsselfaktor dafür, die Benutzeroberfläche reaktionsfähig zu halten. Da die task-Klasse C++-spezifisch ist, müssen Sie eine Windows-Runtime-Schnittstelle verwenden, um asynchrone Vorgänge an andere Komponenten zu übergeben (die möglicherweise in anderen Sprachen als C++ geschrieben sind). Windows-Runtime stellt vier Schnittstellen zum Darstellen von asynchronen Vorgängen bereit:

Der Begriff Aktion bedeutet, dass die asynchrone Aufgabe keinen Wert generiert (denken Sie an eine Funktion, die void zurückgibt). Der Begriff Vorgang bedeutet, dass die asynchrone Aufgabe einen Wert generiert. Der Begriff Status bedeutet, dass die Aufgabe Statusbenachrichtigungen an den Aufrufer übermitteln kann. JavaScript, .NET Framework und Visual C++ bieten jeweils eine eigene Möglichkeit zum Erstellen von Instanzen dieser Schnittstellen zur ABI-übergreifenden Verwendung. Für Visual C++ stellt die Concurrency Runtime die concurrency::create_async-Funktion bereit. Von dieser Funktion wird eine asynchrone Windows-Runtime-Aktion oder ein entsprechender Vorgang zum Darstellen des Abschlusses einer Aufgabe erstellt. Die create_async-Funktion akzeptiert eine Arbeitsfunktion (in der Regel einen Lambda-Ausdruck), erstellt intern ein task-Objekt und umschließt diese Aufgabe mit einer der vier asynchronen Windows-Runtime-Schnittstellen.

Hinweis

Verwenden Sie create_async nur, wenn Sie Funktionalität erstellen müssen, auf die von einer anderen Sprache oder einer anderen Windows-Runtime-Komponente zugegriffen werden kann.Verwenden Sie die task-Klasse direkt, wenn Sie wissen, dass der Vorgang von C++-Code in der gleichen Komponente sowohl erstellt als auch genutzt wird.

Der Rückgabetyp von create_async wird durch den Typ der Argumente bestimmt. Wenn z. B. die Arbeitsfunktion weder einen Wert zurückgibt und noch den Status meldet, wird von create_async eine IAsyncAction zurückgegeben. Wenn die Arbeitsfunktion keinen Wert zurückgibt, jedoch den Status meldet, wird von create_async eine IAsyncActionWithProgress zurückgegeben. Stellen Sie für Statusmeldungen ein concurrency::progress_reporter-Objekt als Parameter der Arbeitsfunktion bereit. Durch Statusbenachrichtigungen kann gemeldet werden, wie viel Arbeit bereits erledigt wurde und wie viel noch verbleibt (beispielsweise als Prozentsatz). Zudem können Ergebnisse gemeldet werden, sobald sie verfügbar sind.

Die IAsyncAction-, IAsyncActionWithProgress<TProgress>-, IAsyncOperation<TResult>- und IAsyncActionOperationWithProgress<TProgress, TProgress>-Schnittstellen bieten jeweils eine Cancel-Methode, die das Abbrechen des asynchronen Vorgangs ermöglicht. Die task-Klasse verwendet Abbruchtoken. Wenn Sie Arbeit mithilfe eines Abbruchtokens abbrechen, wird von der Runtime keine neue Verarbeitung gestartet, die dieses Token abonniert. Für eine bereits aktive Verarbeitung kann das entsprechende Abbruchtoken überwacht und die Verarbeitung zum angegebenen Zeitpunkt beendet werden. Dieser Mechanismus wird ausführlicher im Dokument Abbruch in der PPL beschrieben. Es gibt zwei Möglichkeiten, den Aufgabenabbruch mit den Windows-RuntimeCancel-Methoden zu verbinden. Die erste Möglichkeit besteht darin, die an create_async zu übergebende Arbeitsfunktion so zu definieren, dass diese ein concurrency::cancellation_token-Objekt akzeptiert. Beim Aufrufen der Cancel-Methode wird dieses Abbruchtoken abgebrochen, und für das zugrunde liegende task-Objekt, das den create_async-Aufruf unterstützt, gelten die normale Abbruchregeln. Wenn Sie kein cancellation_token-Objekt bereitstellen, wird dieses vom zugrunde liegenden task-Objekt implizit definiert. Definieren Sie ein cancellation_token-Objekt, wenn auf Abbrüche in der Arbeitsfunktion kooperativ reagiert werden muss. Im Abschnitt Beispiel: Steuern der Ausführung in einer Windows Store-App mit C++ und XAML finden Sie ein Beispiel für das Ausführen von Abbrüchen in einer Windows Store-App mit C# und XAML, in der eine benutzerdefinierte Windows-Runtime-Komponente in C++ verwendet wird.

Warnung

In einer Kette von Aufgabenfortsetzungen sollte stets der Zustand bereinigt und anschließend concurrency::cancel_current_task aufgerufen werden, wenn concurrency::is_task_cancellation_requested den Wert true zurückgibt.Wenn Sie frühzeitig zurückkehren, anstatt cancel_current_task aufzurufen, geht der Vorgang in den Zustand "Abgeschlossen" anstelle von "Abgebrochen" über.

In der folgenden Tabelle werden die Kombinationen zusammengefasst, die Sie zum Definieren asynchroner Vorgänge in Ihrer App verwenden können.

Zu erstellende Windows-Runtime-Schnittstelle

Rückgabe dieses create_async-Typs

Übergabe dieser Parametertypen an die Arbeitsfunktion zur Verwendung als implizites Abbruchtoken

Übergabe dieser Parametertypen an die Arbeitsfunktion zur Verwendung als explizites Abbruchtoken

IAsyncAction

void oder task<void>

(keine)

(cancellation_token)

IAsyncActionWithProgress<TProgress>

void oder task<void>

(progress_reporter)

(progress_reporter, cancellation_token)

IAsyncOperation<TResult>

T oder task<T>

(keine)

(cancellation_token)

IAsyncActionOperationWithProgress<TProgress, TProgress>

T oder task<T>

(progress_reporter)

(progress_reporter, cancellation_token)

Bei der Rückgabe kann es sich um einen Wert oder ein task-Objekt handeln, dass von der an die create_async-Funktion übergebenen Arbeitsfunktion zurückgeben wird. Diese Unterschiede führen zu verschiedenen Verhalten. Bei Rückgabe eines Werts wird die Arbeitsfunktion zur Ausführung auf einem Hintergrundthread mit task umschlossen. Zudem wird von der zugrunde liegenden task ein implizites Abbruchtoken verwendet. Umgekehrt wird die Arbeitsfunktion bei Rückgabe eines task-Objekts synchron ausgeführt. Stellen Sie daher beim Zurückgeben eines task-Objekts sicher, dass alle längeren Vorgänge in der Arbeitsfunktion auch als Aufgaben ausgeführt werden, damit die App reaktionsfähig bleiben kann. Zudem wird von der zugrunde liegenden task kein implizites Abbruchtoken verwendet. Daher müssen Sie die Arbeitsfunktion so definieren, dass diese ein cancellation_token-Objekt akzeptiert, falls bei der Rückgabe eines task-Objekts durch create_async Abbruchunterstützung erforderlich ist.

Im folgenden Beispiel werden die verschiedenen Möglichkeiten zum Erstellen eines IAsyncAction-Objekts dargestellt, das von einer anderen Windows-Runtime-Komponente genutzt werden kann.

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

[Nach oben]

Beispiel: Erstellen einer C++ Windows-Runtime-Komponente und ihre Verwendung von C# aus

Betrachten Sie eine App, in der XAML und C# zum Definieren der Benutzeroberfläche und eine C++ Windows-Runtime-Komponente zum Ausführen rechenintensiver Vorgänge verwendet werden. In diesem Beispiel wird von der C++-Komponente berechnet, bei welchen Zahlen in einem angegebenen Bereich es sich um Primzahlen handelt. Zum Veranschaulichen der Unterschiede zwischen den vier asynchronen Windows-Runtime-Aufgabenschnittstellen erstellen Sie zunächst in Visual Studio eine Leere Projektmappe mit dem Namen Primes. Fügen Sie der Projektmappe dann ein Komponente für Windows-Runtime-Projekt hinzu, und nennen Sie es PrimesLibrary. Fügen Sie der generierten C++-Headerdatei folgenden Code hinzu (in diesem Beispiel wird "Class1.h" in "Primes.h" umbenannt). Jede public-Methode definiert eine der vier asynchronen Schnittstellen. Die Methode, die einen Wert zurückgeben, geben ein Windows::Foundation::Collections::IVector<int>-Objekt zurück. Die den Status meldenden Methoden generieren double-Werte, die den Prozentsatz der abgeschlossenen Gesamtarbeit definieren.

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

Hinweis

Konventionsgemäß enden die Namen asynchroner Methoden in Windows-Runtime in der Regel mit "Async".

Fügen Sie der generierten C++-Quelldatei folgenden Code hinzu (in diesem Beispiel wird "Class1.cpp" in "Primes.cpp" umbenannt). Mit der is_prime-Funktion wird ermittelt, ob es sich bei der Eingabe um eine Primzahl handelt. Mit den verbleibenden Methoden wird die Primes-Klasse implementiert. Für jeden Aufruf von create_async wird eine mit der aufrufenden Methode kompatible Signatur verwendet. Da von Primes::ComputePrimesAsync beispielsweise IAsyncAction zurückgegeben wird, gibt die für create_async bereitgestellte Arbeitsfunktion keinen Wert zurück und akzeptiert kein progress_reporter-Objekt 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;
    });
}

Jede Methode führt zuerst eine Überprüfung aus, um sicherzustellen, dass die Eingabeparameter nicht negativ sind. Wenn der Eingabewert negativ ist, gibt die Methode Platform::InvalidArgumentException aus. Die Fehlerbehandlung wird weiter unten in diesem Abschnitt erläutert.

Um diese Methoden in einer Windows Store-App zu nutzen, fügen Sie der Visual Studio-Projektmappe mithilfe der Visual C#-Vorlage Leere App (XAML) ein zweites Projekt hinzu. In diesem Beispiel wird das Projekt Primes genannt. Fügen Sie anschließend im Projekt Primes einen Verweis auf das Projekt PrimesLibrary hinzu.

Fügen Sie "MainPage.xaml" den folgenden Code hinzu. Durch diesen Code wird die Benutzeroberfläche definiert, damit Sie die C++-Komponente aufrufen und Ergebnisse anzeigen können.

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

Fügen Sie der MainPage-Klasse in "MainPage.xaml" den folgenden Code hinzu. Durch diesen Code werden ein Primes-Objekt und die Ereignishandler für Schaltflächen definiert.

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

Von diesen Methoden wird mithilfe des async-Schlüsselworts und des await-Schlüsselworts die Benutzeroberfläche aktualisiert, nachdem die asynchronen Vorgänge abgeschlossen wurden. Weitere Informationen über die asynchronen Muster, die für C# und Visual Basic zur Verfügung stehen, finden Sie unter Asynchrone Muster in Windows Store-Apps mit C# und Asynchrone Muster in Windows Store-Apps mit VB.

Die getPrimesCancellation-Methode und die cancelGetPrimes-Methode werden zusammen verwendet, damit der Benutzer den Vorgang abbrechen kann. Wenn der Benutzer die Schaltfläche Abbrechen auswählt, ruft die cancelGetPrimes-Methode IAsyncOperationWithProgress<TResult, TProgress>::Cancel auf, um den Vorgang abzubrechen. Von der Concurrency Runtime, die den zugrunde liegenden asynchronen Vorgang verwaltet, wird ein interner Ausnahmetyp ausgelöst, der von Windows-Runtime abgefangen wird, um den Abschluss des Abbrechens zu melden. Weitere Informationen zum Abbruchmodell finden Sie unter Abbruch in der PPL.

Wichtig

Damit von der Concurrency Runtime das Abbrechen des Vorgangs ordnungsgemäß an Windows-Runtime gemeldet werden kann, fangen Sie diesen internen Ausnahmetyp nicht ab.Dies bedeutet auch, dass nicht alle Ausnahmen abgefangen werden sollten(catch (...)).Wenn alle Ausnahmen abfangen werden müssen, lösen Sie die Ausnahme erneut aus, um sicherzustellen, dass der Abbruchvorgang von Windows-Runtime abgeschlossen werden kann.

In der folgenden Abbildung wird die Primes-App nach Auswahl aller Optionen dargestellt.

Windows Store-Primes-App

Beispiel, die create_async verwenden, um asynchrone Tasks zu erstellen, die von anderen Sprachen genutzt werden können, finden Sie unter Verwenden von C++ am Beispiel des Reise-Optimierers von Bing Maps und Asynchrone Vorgänge unter Windows 8 in C++ mit PPL.

[Nach oben]

Steuern des Ausführungs-Threads

Von Windows-Runtime wird das COM-Threadmodell verwendet. In diesem Modell werden Objekte in unterschiedlichen Apartments gehostet, abhängig von der jeweiligen Synchronisierungsmethode. Threadsichere Objekte werden im Multithread-Apartment (MTA) gehostet. Objekte, auf die von einem einzelnen Thread zugegriffen werden muss, werden in einem Singlethread-Apartment (STA) gehostet.

In einer App mit Benutzeroberfläche ist der ASTA (Application STA)-Thread für das Verschieben von Fenstermeldungen zuständig und ist der einzige Thread im Prozess, von dem die STA-gehosteten UI-Steuerelemente aktualisiert werden können. Dies hat zwei Auswirkungen. Erstens sollten alle CPU-intensiven und E/A-Vorgänge nicht im ASTA-Thread ausgeführt werden, damit die App reaktionsfähig bleibt. Zweitens müssen von Hintergrundthreads stammende Ergebnisse zurück in das ASTA gemarshallt werden, um die Benutzeroberfläche zu aktualisieren. In einer Windows Store-App mit C++ werden MainPage und alle anderen XAML-Seiten im ASTA ausgeführt. Daher werden im ASTA deklarierte Aufgabenfortsetzungen standardmäßig dort ausgeführt, sodass Steuerelemente direkt im Fortsetzungstext aktualisiert werden können. Wenn Sie jedoch eine Aufgabe in einer anderen Aufgabe schachteln, werden alle Fortsetzungen dieser geschachtelten Aufgabe im MTA ausgeführt. Daher sollten Sie berücksichtigen, ob der Ausführungskontext dieser Fortsetzungen explizit angegeben werden muss.

Dank einer besonderen Semantik können die Threaddetails bei durch einen asynchronen Vorgang wie IAsyncOperation<TResult> erstellten Aufgaben ignoriert werden. Obwohl ein Vorgang möglicherweise in einem Hintergrundthread ausgeführt wird (oder überhaupt nicht von einem Thread unterstützt wird), erfolgen dessen Fortsetzungen in jedem Fall standardmäßig in dem Apartment, von dem die Fortsetzungsvorgänge gestartet wurden (d. h. im Apartment, von dem task::then aufgerufen wurde). Mithilfe der concurrency::task_continuation_context-Klasse kann der Ausführungskontext einer Fortsetzung gesteuert werden. Verwenden Sie zum Erstellen von task_continuation_context-Objekten die folgenden statischen Hilfsmethoden:

Sie können der task::then-Methode ein task_continuation_context-Objekt übergeben, um den Ausführungskontext der Fortsetzung explizit zu steuern. Alternativ können Sie die Aufgabe an ein anderes Apartment übergeben und dann die task::then-Methode aufrufen, um den Ausführungskontext implizit zu steuern.

Wichtig

Da der Haupt-UI-Thread von Windows Store-Apps im STA ausgeführt wird, werden in diesem STA erstellte Fortsetzungen standardmäßig ebenfalls im STA ausgeführt.Entsprechend werden im MTA erstellte Fortsetzungen auch im MTA ausgeführt.

Im folgenden Abschnitt wird eine App dargestellt, die eine Datei auf dem Datenträger liest, die häufigsten Wörter in dieser Datei sucht und die Ergebnisse anschließend auf der Benutzeroberfläche anzeigt. Der letzte Vorgang (die Aktualisierung der Benutzeroberfläche) erfolgt im UI-Thread.

Wichtig

Dieses Verhalten ist für Windows Store-Apps spezifisch.Bei Desktop-Apps wird die Ausführung von Fortsetzungen nicht von Ihnen gesteuert.Stattdessen wird vom Planer ein Arbeitsthread zur Ausführung der einzelnen Fortsetzungen ausgewählt.

Wichtig

Rufen Sie nicht concurrency::task::wait im Text einer Fortsetzung auf, die im STA ausgeführt wird.Andernfalls löst die Laufzeit concurrency::invalid_operation aus, da diese Methode den aktuellen Thread blockiert und die App dadurch möglicherweise nicht mehr reagiert.Sie können jedoch die concurrency::task::get-Methode aufrufen, um das Ergebnis der Vorgängeraufgabe in einer aufgabenbasierten Fortsetzung zu erhalten.

[Nach oben]

Beispiel: Steuern der Ausführung in einer Windows Store-App mit C++ und XAML

Betrachten Sie eine App mit C++ und XAML, die eine Datei auf dem Datenträger liest, die häufigsten Wörter in dieser Datei sucht und die Ergebnisse anschließend auf der Benutzeroberfläche anzeigt. Zum Entwickeln dieser App erstellen Sie in Visual Studio zunächst mithilfe der Vorlage Windows StoreLeere App (XAML) ein -Projekt und nennen dieses CommonWords. Geben Sie im App-Manifest die Dokumentbibliothek-Funktion an, damit die App auf den Ordner "Dokumente" zugreifen kann. Fügen Sie im Deklarationsabschnitt des App-Manifests außerdem den Textdateityp (.txt) hinzu. Weitere Informationen zu App-Funktionen und -Deklarationen finden Sie unter App-Pakete und -Bereitstellung (Windows Store-Apps).

Aktualisieren Sie in "MainPage.xaml" das Grid-Element mit einem ProgressRing-Element und einem TextBlock-Element. ProgressRing gibt an, dass der Vorgang ausgeführt wird, und TextBlock zeigt die Ergebnisse der Berechnung an.

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

Fügen Sie "pch.h" die folgenden #include-Anweisungen hinzu.

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

Fügen Sie der MainPage-Klasse ("MainPage.h") die folgenden Methodendeklarationen hinzu.

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

Fügen Sie "MainPage.cpp" die folgenden using-Anweisungen hinzu.

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

Implementieren Sie in "MainPage.cpp" die Methoden MainPage::MakeWordList, MainPage::FindCommonWords und MainPage::ShowResults. Von MainPage::MakeWordList und MainPage::FindCommonWords werden rechenintensive Vorgänge ausgeführt. Von der MainPage::ShowResults-Methode wird das Ergebnis der Berechnung auf der Benutzeroberfläche angezeigt.

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

Ändern Sie den MainPage-Konstruktor, um eine Kette von Fortsetzungsaufgaben zu erstellen, von denen häufige Wörter im Buch Die Ilias von Homer auf der Benutzeroberfläche angezeigt werden. Die ersten beiden Fortsetzungsaufgaben, von denen der Text in einzelne Wörter unterteilt und nach häufigen Wörtern durchsucht wird, können zeitaufwändig sein. Daher wird für diese Aufgaben explizit die Ausführung im Hintergrund festgelegt. Für die letzte Fortsetzungsaufgabe zur Aktualisierung der Benutzeroberfläche wird kein Fortsetzungskontext angegeben, weshalb hierbei die Apartmentthreadregeln befolgt werden.

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

Hinweis

In diesem Beispiel wird das Angeben eines Ausführungskontexts und Zusammenstellen einer Kette von Fortsetzungen veranschaulicht.Denken Sie daran, dass die Fortsetzungen einer durch einen asynchronen Vorgang erstellten Aufgabe standardmäßig in dem Apartment ausgeführt werden, von dem task::then aufgerufen wurde.In diesem Beispiel wird daher durch task_continuation_context::use_arbitrary angegeben, dass Vorgänge, die nicht die Benutzeroberfläche betreffen, in einem Hintergrundthread ausgeführt werden.

In der folgenden Abbildung werden die Ergebnisse der CommonWords-App dargestellt.

Windows Store-CommonWords-App

In diesem Beispiel werden Abbruchvorgänge unterstützt, da die task-Objekte zur Unterstützung von create_async ein implizites Abbruchtoken verwenden. Wenn die Aufgaben kooperativ auf Abbruchvorgänge reagieren sollen, definieren Sie die Arbeitsfunktion so, dass diese ein cancellation_token-Objekt akzeptiert. Weitere Informationen zum Abbrechen in der PPL finden Sie unter Abbruch in der PPL.

[Nach oben]

Siehe auch

Konzepte

Concurrency Runtime