Erstellen von asynchronen Vorgängen in C++ für UWP-Apps
In diesem Dokument werden einige der wichtigsten Punkte beschrieben, die Sie berücksichtigen sollten, wenn Sie die Aufgabenklasse zum Erstellen asynchroner Windows ThreadPool-basierter Vorgänge in einer Universellen Windows-Runtime-App (UWP) verwenden.
Die Verwendung der asynchronen Programmierung ist eine wichtige Komponente im Windows-Runtime App-Modell, da apps auf Benutzereingaben reagieren können. 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. Das Dokument asynchrone Programmierung in C++ bietet eine Übersicht über das asynchrone Muster, das in Visual C++ zum Erstellen von UWP-Apps verfügbar ist. In diesem Dokument erfahren Sie, wie Sie sowohl asynchrone Windows-Runtime-Vorgänge nutzen als auch Ketten erstellen. In diesem Abschnitt wird beschrieben, wie Sie die Typen in ppltasks.h verwenden, um asynchrone Vorgänge zu erstellen, die von einer anderen Windows-Runtime Komponente genutzt werden können und wie Sie steuern, wie asynchrone Arbeit ausgeführt wird. Lesen Sie auch Asynchrone Programmiermuster und Tipps in Hilo (Windows Store-Apps mit C++ und XAML), um zu erfahren, wie wir die Aufgabenklasse verwendet haben, um asynchrone Vorgänge in Hilo, einer Windows-Runtime-App mit C++ und XAML zu implementieren.
Hinweis
Sie können die Parallel Patterns Library (PPL) und die asynchrone Agents-Bibliothek in einer UWP-App verwenden. Aufgabenplaner oder Ressourcen-Manager können jedoch nicht verwendet werden. In diesem Dokument werden zusätzliche Features beschrieben, die von der PPL bereitgestellt werden, die nur für eine UWP-App und nicht für eine Desktop-App 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 (entwedertask<T>
odertask<void>
) oder synchron in dem Kontext ausgeführt wird, in demcreate_async
aufgerufen wurde. Eine Arbeitsfunktion, dieT
odervoid
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 UWP-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 aus einer asynchronen Aktion erstellt wird, wird standardmäßig ein beliebiger Kontext für die Fortsetzungen der Aufgabe verwendet. Sie können den Standardkontext mit der concurrency::task_continuation_context -Klasse überschreiben.
Inhalt dieses Dokuments
Beispiel: Erstellen einer C++-Komponente für Windows-Runtime
Beispiel: Steuern der Ausführung in einer Windows-Runtime 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 Task Parallelismaufgerufen wurde.
Die Windows-Runtime ist eine Programmierschnittstelle, mit der Sie UWP-Apps erstellen können, die nur in einer speziellen Betriebssystemumgebung ausgeführt werden. Solche Apps verwenden autorisierte Funktionen, Datentypen und Geräte, und sie werden über den Microsoft Store verteilt. Die Windows-Runtime wird durch die Application Binary Interface (ABI) dargestellt. Die ABI ist ein zugrunde liegender binärer Vertrag, der Windows-Runtime APIs programmiersprachen wie Visual C++ zur Verfügung stellt.
Mithilfe der Windows-Runtime können Sie die besten Features 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 spezifisch für C++ ist, müssen Sie eine Windows-Runtime Schnittstelle verwenden, um asynchrone Vorgänge mit anderen Komponenten zu kommunizieren (die in anderen Sprachen als C++ geschrieben werden können). Die Windows-Runtime stellt vier Schnittstellen bereit, mit denen Sie asynchrone Vorgänge darstellen können:
Windows::Foundation::IAsyncAction
Stellt eine asynchrone Aktion dar.
Windows::Foundation::IAsyncActionWithProgress TProgress<>
Stellt eine asynchrone Aktion für Statusbenachrichtigungen dar.
Windows::Foundation::IAsyncOperation<TResult>
Stellt einen asynchronen Vorgang dar, der ein Ergebnis zurückgibt.
Windows::Foundation::IAsyncOperationWithProgress<TResult, TProgress>
Stellt einen asynchronen Vorgang dar, der ein Ergebnis zurückgibt und den Status meldet.
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 PPL die concurrency::create_async -Funktion bereit. Diese Funktion erstellt eine Windows-Runtime asynchrone Aktion oder operation, die den Abschluss einer Aufgabe darstellt. Die create_async
Funktion verwendet eine Arbeitsfunktion (in der Regel ein Lambda-Ausdruck), erstellt intern ein task
Objekt und umschließt diese Aufgabe in einer der vier asynchronen Windows-Runtime-Schnittstellen.
Hinweis
Verwenden Sie create_async
nur, wenn Sie Funktionen erstellen müssen, auf die über eine andere Sprache oder eine andere 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. Im Dokument Cancellation in the PPLwird dieser Mechanismus ausführlich beschrieben. Sie können den Vorgangsabbruch auf zwei Arten mit den Windows-Runtime Cancel
Methoden 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. Wenn die Cancel
Methode aufgerufen wird, wird dieses Abbruchtoken abgebrochen, und die normalen Abbruchregeln gelten für das zugrunde liegende task
Objekt, das den create_async
Aufruf unterstützt. 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. Das Abschnittsbeispiel: Steuern der Ausführung in einer Windows-Runtime App mit C++ und XAML zeigt ein Beispiel für das Ausführen eines Abbruchs in einer Universelle Windows-Plattform -App (UWP) mit C# und XAML, die eine benutzerdefinierte Windows-Runtime C++-Komponente verwendet.
Warnung
Bereinigen Sie in einer Kette von Aufgabenfortsetzungen immer den Zustand, und rufen Sie dann die Parallelität::cancel_current_task auf, wenn das Abbruchtoken abgebrochen wird. 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.
So erstellen Sie diese 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> |
(none) | (cancellation_token ) |
IAsyncActionWithProgress<TProgress> |
void oder task<void> |
(progress_reporter ) |
(progress_reporter , cancellation_token ) |
IAsyncOperation<TResult> |
T oder task<T> |
(none) | (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.
Das folgende Beispiel zeigt die verschiedenen Möglichkeiten zum Erstellen eines IAsyncAction
Objekts, 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.
});
});
Beispiel: Erstellen einer C++-Windows-Runtime Komponente und Verwenden von C#
Erwägen Sie eine App, die XAML und C# verwendet, um die Benutzeroberfläche und eine C++-Windows-Runtime-Komponente zu definieren, um rechenintensive Vorgänge auszuführen. In diesem Beispiel wird von der C++-Komponente berechnet, bei welchen Zahlen in einem angegebenen Bereich es sich um Primzahlen handelt. Um die Unterschiede zwischen den vier Windows-Runtime asynchronen Aufgabenschnittstellen zu veranschaulichen, starten Sie in Visual Studio, indem Sie eine leere Lösung erstellen und benennenPrimes
. Fügen Sie der Projektmappe dann ein Projekt für Windows-Runtime-Komponente 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 Methoden, 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
Standardmäßig enden asynchrone Methodennamen im 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 zunächst eine Überprüfung durch, um sicherzustellen, dass die Eingabeparameter nicht negativ sind. Im Fall eines negativen Eingabewerts wird von der Methode Platform::InvalidArgumentExceptionausgelöst. Die Fehlerbehandlung wird weiter unten in diesem Abschnitt erläutert.
Um diese Methoden aus einer UWP-App zu nutzen, verwenden Sie die Xaml-Vorlage (Visual C# Blank App), um der Visual Studio-Projektmappe ein zweites Projekt hinzuzufügen. 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="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>
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. Informationen zum asynchronen Codieren in UWP-Apps finden Sie unter Threading und asynchrone Programmierung.
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ähnt, ruft die cancelGetPrimes
Methode IAsyncOperationWithProgress<TResult, TProgress>::Cancel auf, um den Vorgang abzubrechen. Die Parallelitätslaufzeit, die den zugrunde liegenden asynchronen Vorgang verwaltet, löst einen internen Ausnahmetyp aus, der vom Windows-Runtime abgefangen wird, um diesen Abbruch zu kommunizieren. Weitere Informationen zum Abbruchmodell finden Sie unter "Abbruch".
Wichtig
Damit die PPL den Windows-Runtime ordnungsgemäß melden kann, den Vorgang abgebrochen hat, fangen Sie diesen internen Ausnahmetyp nicht ab. Dies bedeutet auch, dass nicht alle Ausnahmen abgefangen werden sollten(catch (...)
). Wenn Sie alle Ausnahmen abfangen müssen, wiederholen Sie die Ausnahme, um sicherzustellen, dass die Windows-Runtime den Abbruchvorgang abschließen kann.
In der folgenden Abbildung wird die Primes
-App nach Auswahl aller Optionen dargestellt.
Ein Beispiel zum Erstellen asynchroner create_async
Aufgaben, die von anderen Sprachen genutzt werden können, finden Sie unter Verwenden von C++ im Bing Maps Trip Optimizer-Beispiel.
Steuern des Ausführungs-Threads
Die Windows-Runtime verwendet das COM-Threadingmodell. 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 C++-UWP-App MainPage
und anderen XAML-Seiten werden alle auf der ATSA 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:
Mithilfe von concurrency::task_continuation_context::use_arbitrary geben Sie an, dass die Fortsetzung in einem Hintergrundthread ausgeführt wird.
Mithilfe von concurrency::task_continuation_context::use_current geben Sie an, dass die Fortsetzung in dem Thread ausgeführt wird, von dem
task::then
aufgerufen wurde.
Sie können der task_continuation_context
task::then -Methode ein -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 UWP-Apps unter STA ausgeführt wird, werden fortsetzungen, die Sie für dieses STA erstellen, standardmäßig auf dem 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 spezifisch für UWP-Apps. 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.
Beispiel: Steuern der Ausführung in einer Windows-Runtime 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. Um diese App zu erstellen, starten Sie in Visual Studio, indem Sie ein Leeres App-Projekt (Universelle Windows-App) erstellen und es CommonWords
benennen. 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 Packen, Bereitstellen und Abfragen von Windows-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 die folgenden #include
Anweisungen zu pch.h 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.
In diesem Beispiel ist es möglich, den Abbruch zu unterstützen, da die Objekte, die task
ein implizites Abbruchtoken unterstützen, verwenden create_async
. 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 Cancellation in the PPL.