Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Questo documento descrive alcuni dei punti chiave da tenere presenti quando si usa la classe attività per produrre operazioni asincrone basate su ThreadPool di Windows in un'app UWP (Universal Windows Runtime).
L'uso della programmazione asincrona è un componente chiave nel modello di app di Windows Runtime perché consente alle app di rimanere reattive all'input dell'utente. È possibile avviare un'attività a esecuzione prolungata senza bloccare il thread dell'interfaccia utente ed è possibile ricevere i risultati dell'attività in un secondo momento. È anche possibile annullare le attività e ricevere notifiche sullo stato di avanzamento durante l'esecuzione delle attività in background. Il documento Programmazione asincrona in C++ offre una panoramica del modello asincrono disponibile in Visual C++ per creare app UWP. Questo documento illustra come utilizzare e creare catene di operazioni asincrone di Windows Runtime. Questa sezione descrive come usare i tipi in ppltasks.h per produrre operazioni asincrone che possono essere utilizzate da un altro componente Windows Runtime e come controllare il funzionamento asincrono. È anche consigliabile leggere modelli di programmazione asincroni e suggerimenti in Hilo (app di Windows Store che usano C++ e XAML) per scoprire come è stata usata la classe di attività per implementare operazioni asincrone in Hilo, un'app di Windows Runtime con C++ e XAML.
Annotazioni
Puoi usare la libreria PPL ( Parallel Patterns Library ) e la libreria degli agenti asincroni in un'app UWP. Non è tuttavia possibile usare l'Utilità di pianificazione o il Gestore Risorse. Questo documento descrive le funzionalità aggiuntive fornite dal PPL disponibili solo per un'app UWP e non per un'app desktop.
Punti chiave
Usare concurrency::create_async per creare operazioni asincrone che possono essere usate da altri componenti (che potrebbero essere scritti in linguaggi diversi da C++).
Usare concurrency::progress_reporter per segnalare le notifiche di stato ai componenti che chiamano le operazioni asincrone.
Usare i token di annullamento per consentire l'annullamento delle operazioni asincrone interne.
Il comportamento della funzione
create_asyncdipende dal tipo di ritorno della funzione di elaborazione che le viene passata. Una funzione di lavoro che restituisce un'attività (task<T>otask<void>) viene eseguita in modo sincrono nel contesto di chiamatacreate_async. Funzione di lavoro che restituisceTovoidviene eseguita in un contesto arbitrario.È possibile usare il metodo concurrency::task::then per creare una catena di attività che vengono eseguite una dopo l'altra. In un'app UWP il contesto predefinito per le continuazioni di un'attività dipende dal modo in cui è stata costruita l'attività. Se l'attività è stata creata passando un'azione asincrona al costruttore dell'attività o passando un'espressione lambda che restituisce un'azione asincrona, il contesto predefinito per tutte le continuazioni di tale attività è il contesto corrente. Se l'attività non viene costruita da un'azione asincrona, per impostazione predefinita viene usato un contesto arbitrario per le continuazioni dell'attività. È possibile eseguire l'override del contesto predefinito con la classe concurrency::task_continuation_context .
In questo documento
Creazione di operazioni asincrone
È possibile usare l'attività e il modello di continuazione nella libreria PPL (Parallel Patterns Library) per definire le attività in background e le attività aggiuntive eseguite al termine dell'attività precedente. Questa funzionalità viene fornita dalla classe concurrency::task . Per altre informazioni su questo modello e sulla task classe , vedere Parallelismo delle attività.
Windows Runtime è un'interfaccia di programmazione che puoi usare per creare app UWP eseguite solo in un ambiente del sistema operativo speciale. Tali app usano funzioni autorizzate, tipi di dati e dispositivi e vengono distribuite da Microsoft Store. Windows Runtime è rappresentato dall'interfaccia ABI (Application Binary Interface ). L'ABI è un contratto binario sottostante che rende le API di Windows Runtime disponibili per i linguaggi di programmazione, ad esempio Visual C++.
Usando Windows Runtime, puoi usare le migliori funzionalità di vari linguaggi di programmazione e combinarle in un'unica app. Ad esempio, è possibile creare l'interfaccia utente in JavaScript ed eseguire la logica delle app a elevato utilizzo di calcolo in un componente C++. La possibilità di eseguire queste operazioni a elevato utilizzo di calcolo in background è un fattore chiave per mantenere reattiva l'interfaccia utente. Poiché la task classe è specifica di C++, è necessario usare un'interfaccia di Windows Runtime per comunicare operazioni asincrone ad altri componenti (che potrebbero essere scritti in linguaggi diversi da C++). Windows Runtime offre quattro interfacce che è possibile usare per rappresentare le operazioni asincrone:
Windows::Foundation::IAsyncAction
Rappresenta un'azione asincrona.
Windows::Foundation::IAsyncActionWithProgress TProgress<>
Rappresenta un'azione asincrona che segnala lo stato di avanzamento.
Windows::Foundation::IAsyncOperation<TResult>
Rappresenta un'operazione asincrona che restituisce un risultato.
Windows::Foundation::IAsyncOperationWithProgress<TResult, TProgress>
Rappresenta un'operazione asincrona che restituisce un risultato e segnala lo stato di avanzamento.
Il concetto di un'azione significa che l'attività asincrona non produce un valore (si pensi a una funzione che restituisce void). Il concetto di operazione significa che l'attività asincrona produce un valore. Il concetto di progresso significa che l'attività può segnalare i messaggi di avanzamento al chiamante. JavaScript, .NET Framework e Visual C++ consentono di creare istanze di queste interfacce da usare attraverso il limite ABI. Per Visual C++, il PPL fornisce la funzione concurrency::create_async . Questa funzione crea un'azione o un'operazione asincrona di Windows Runtime che rappresenta il completamento di un'attività. La create_async funzione accetta una funzione di lavoro (in genere un'espressione lambda), crea internamente un task oggetto ed esegue il wrapping di tale attività in una delle quattro interfacce di Windows Runtime asincrone.
Annotazioni
Usare create_async solo quando è necessario creare funzionalità accessibili da un altro linguaggio o da un altro componente di Windows Runtime. Usare direttamente la task classe quando si sa che l'operazione viene prodotta e utilizzata dal codice C++ nello stesso componente.
Il tipo restituito di create_async è determinato dal tipo dei relativi argomenti. Ad esempio, se la funzione di lavoro non restituisce un valore e non segnala lo stato di avanzamento, create_async restituisce IAsyncAction. Se la funzione di lavoro non restituisce un valore e segnala anche lo stato di avanzamento, create_async restituisce IAsyncActionWithProgress. Per segnalare lo stato di avanzamento, specificare un oggetto concurrency::progress_reporter come parametro per la funzione di elaborazione. La possibilità di segnalare lo stato di avanzamento consente di segnalare quale quantità di lavoro è stata eseguita e quale quantità rimane (ad esempio, come percentuale). Consente inoltre di segnalare i risultati non appena diventano disponibili.
Ognuna delle interfacce IAsyncAction, IAsyncActionWithProgress<TProgress>, IAsyncOperation<TResult> e IAsyncActionOperationWithProgress<TProgress, TProgress> fornisce un metodo Cancel che consente di annullare l'operazione asincrona. La task classe funziona con i token di annullamento. Quando si usa un token di annullamento per annullare il lavoro, il runtime non avvia nuove operazioni che sottoscrivono tale token. Il lavoro già attivo può monitorare il token di annullamento e fermarsi quando possibile. Questo meccanismo è descritto in modo più dettagliato nel documento Annullamento nel PPL. Puoi connettere l'annullamento delle attività con i metodi di Windows Runtime Cancel in due modi. In primo luogo, puoi definire la funzione di lavoro che passi a create_async in modo che accetti un oggetto concurrency::cancellation_token. Quando viene chiamato il Cancel metodo , questo token di annullamento viene annullato e le normali regole di annullamento si applicano all'oggetto sottostante task che supporta la create_async chiamata. Se non si specifica un cancellation_token oggetto , l'oggetto sottostante task ne definisce uno in modo implicito. Definisci un oggetto cancellation_token quando è necessario rispondere in modo cooperativo all'annullamento nella funzione di lavoro. La sezione Esempio: Controllo dell'esecuzione in un'app di Windows Runtime con C++ e XAML mostra un esempio di come eseguire l'annullamento in un'app UWP (Universal Windows Platform) con C# e XAML che usa un componente C++ di Windows Runtime personalizzato.
Avvertimento
In una catena di continuazioni di attività, pulisci sempre lo stato e quindi chiama concurrency::cancel_current_task quando il token di annullamento viene annullato. Se si restituisce in anticipo anziché chiamare cancel_current_task, l'operazione passa allo stato completato anziché allo stato annullato.
La tabella seguente riepiloga le combinazioni che è possibile usare per definire operazioni asincrone nell'app.
| Per creare questa interfaccia di Windows Runtime | Restituisce questo tipo da create_async |
Passare questi tipi di parametro alla funzione di lavoro per usare un token di annullamento implicito | Passare questi tipi di parametro alla funzione di lavoro per usare un token di annullamento esplicito |
|---|---|---|---|
IAsyncAction |
void o task<void> |
(nessuno) | (cancellation_token) |
IAsyncActionWithProgress<TProgress> |
void o task<void> |
(progress_reporter) |
(progress_reporter, cancellation_token) |
IAsyncOperation<TResult> |
T o task<T> |
(nessuno) | (cancellation_token) |
IAsyncActionOperationWithProgress<TProgress, TProgress> |
T o task<T> |
(progress_reporter) |
(progress_reporter, cancellation_token) |
È possibile restituire un valore o un task oggetto dalla funzione operativa passata alla create_async funzione. Queste varianti producono comportamenti diversi. Quando si restituisce un valore, la funzione di lavoro viene racchiusa in un task così che possa essere eseguita in un thread in background. Inoltre, l'oggetto sottostante task usa un token di annullamento implicito. Viceversa, se si restituisce un task oggetto , la funzione di lavoro viene eseguita in modo sincrono. Pertanto, se si restituisce un oggetto task, assicurarsi che le operazioni lunghe nella funzione di lavoro si eseguano anche come attività per consentire all'app di rimanere reattiva. Inoltre, l'oggetto sottostante task non usa un token di annullamento implicito. Pertanto, è necessario definire la funzione di lavoro per accettare un cancellation_token oggetto se è necessario il supporto per l'annullamento quando si restituisce un task oggetto da create_async.
L'esempio seguente illustra i vari modi per creare un IAsyncAction oggetto che può essere utilizzato da un altro componente Windows Runtime.
// Creates an IAsyncAction object and uses an implicit cancellation token.
auto op1 = create_async([]
{
// Define work here.
});
// Creates an IAsyncAction object and uses no cancellation token.
auto op2 = create_async([]
{
return create_task([]
{
// Define work here.
});
});
// Creates an IAsyncAction object and uses an explicit cancellation token.
auto op3 = create_async([](cancellation_token ct)
{
// Define work here.
});
// Creates an IAsyncAction object that runs another task and also uses an explicit cancellation token.
auto op4 = create_async([](cancellation_token ct)
{
return create_task([ct]()
{
// Define work here.
});
});
Esempio: Creare un componente Windows Runtime C++ e consumarlo da C#
Si consideri un'app che usa XAML e C# per definire l'interfaccia utente e un componente Windows Runtime C++ per eseguire operazioni a elevato utilizzo di calcolo. In questo esempio, il componente C++ calcola quali numeri in un determinato intervallo siano primi. Per illustrare le differenze tra le quattro interfacce di attività asincrone di Windows Runtime, avviare, in Visual Studio, creando una soluzione vuota e assegnandogli Primesil nome . Aggiungere quindi alla soluzione un progetto componente Windows Runtime e denominarlo PrimesLibrary. Aggiungere il codice seguente al file di intestazione C++ generato (in questo esempio viene rinominato Class1.h in Primes.h). Ogni public metodo definisce una delle quattro interfacce asincrone. I metodi che restituiscono un valore restituiscono un oggetto Windows::Foundation::Collections::IVector<int> . I metodi che segnalano lo stato di avanzamento producono double valori che definiscono la percentuale del lavoro complessivo completato.
#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);
};
}
Annotazioni
Per convenzione, i nomi dei metodi asincroni in Windows Runtime terminano in genere con "Async".
Aggiungere il codice seguente al file di origine C++ generato(in questo esempio viene rinominato Class1.cpp in Primes.cpp). La is_prime funzione determina se l'input è primo. I metodi rimanenti implementano la Primes classe . Ogni chiamata a create_async usa una firma compatibile con il metodo da cui viene chiamato. Ad esempio, poiché Primes::ComputePrimesAsync restituisce IAsyncAction, la funzione di lavoro fornita a create_async non restituisce un valore e non accetta un progress_reporter oggetto come parametro.
// 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;
});
}
Ogni metodo esegue prima la convalida per assicurarsi che i parametri di input siano non negativi. Se un valore di input è negativo, il metodo genera Platform::InvalidArgumentException. La gestione degli errori viene illustrata più avanti in questa sezione.
Per usare questi metodi da un'app UWP, usa il modello App vuota (XAML) di Visual C# per aggiungere un secondo progetto alla soluzione Visual Studio. Questo esempio assegna al progetto il nome Primes. Quindi, dal Primes progetto, aggiungere un riferimento al PrimesLibrary progetto.
Aggiungere il codice seguente a MainPage.xaml. Questo codice definisce l'interfaccia utente in modo da poter chiamare il componente C++ e visualizzare i risultati.
<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>
Aggiungere il codice seguente alla MainPage classe in MainPage.xaml. Questo codice definisce un Primes oggetto e i gestori eventi del pulsante.
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();
}
Questi metodi usano le async parole chiave e await per aggiornare l'interfaccia utente al termine delle operazioni asincrone. Per informazioni sulla codifica asincrona nelle app UWP, vedi Threading e programmazione asincrona.
I getPrimesCancellation metodi e cancelGetPrimes interagiscono per consentire all'utente di annullare l'operazione. Quando l'utente sceglie il pulsante Annulla , il cancelGetPrimes metodo chiama IAsyncOperationWithProgress<TResult, TProgress>::Cancel per annullare l'operazione. Il Concurrency Runtime, che gestisce l'operazione asincrona sottostante, genera un tipo di eccezione interna che viene intercettata dal Windows Runtime per comunicare che l'annullamento è stato completato. Per altre informazioni sul modello di annullamento, vedere Annullamento.
Importante
Per consentire al PPL di segnalare correttamente a Windows Runtime che l'operazione è stata annullata, non intercettare questo tipo di eccezione interna. Ciò significa che non dovresti intercettare tutte le eccezioni (catch (...)). Se è necessario intercettare tutte le eccezioni, generare nuovamente l'eccezione per assicurarsi che Windows Runtime possa completare l'operazione di annullamento.
La figura seguente mostra l'app Primes dopo aver scelto ogni opzione.
Per un esempio che usa create_async per creare attività asincrone che possono essere utilizzate da altri linguaggi, vedere Utilizzo di C++ nell'esempio di Ottimizzazione del viaggio di Bing Maps.
Controllo del thread di esecuzione
Windows Runtime usa il modello di threading COM. In questo modello gli oggetti sono ospitati in appartamenti diversi, a seconda della modalità di gestione della sincronizzazione. Gli oggetti thread-safe sono ospitati nell'appartamento multithread (MTA). Gli oggetti a cui è necessario accedere da un singolo thread sono ospitati in un appartamento a thread singolo (STA).
In un'app che dispone di un'interfaccia utente, il thread ASTA (Application STA) è responsabile della gestione dei messaggi delle finestre ed è l'unico thread nel processo in grado di aggiornare i controlli dell'interfaccia utente ospitati da STA. Questo ha due conseguenze. Prima di tutto, per consentire all'app di rimanere reattivo, tutte le operazioni con utilizzo intensivo della CPU e I/O non devono essere eseguite nel thread ASTA. In secondo luogo, per aggiornare l'interfaccia utente, i risultati provenienti dai thread in background devono essere sottoposti a marshalling all'ASTA. In un'applicazione UWP C++ e nelle altre pagine XAML, tutto viene eseguito su ATSA. Pertanto, le continuazioni di attività dichiarate nell'ASTA vengono eseguite lì per impostazione predefinita, in modo da poter aggiornare i controlli direttamente nel corpo della continuazione. Tuttavia, se si annida un'attività in un'altra attività, tutte le continuazioni nell'attività nidificata vengono eseguite nell'MTA. Pertanto, è necessario valutare se specificare in modo esplicito il contesto in cui vengono eseguite queste continuazioni.
Un'attività creata da un'operazione asincrona, ad esempio IAsyncOperation<TResult>, usa una semantica speciale che consente di ignorare i dettagli del threading. Sebbene un'operazione possa essere eseguita su un thread in background (o potrebbe non essere supportata da un thread), per impostazione predefinita è garantito che le sue continuazioni vengano eseguite nell'appartamento che ha avviato le operazioni di continuazione (in altre parole, dall'appartamento che ha chiamato task::then). È possibile usare la classe concurrency::task_continuation_context per controllare il contesto di esecuzione di una continuazione. Usare questi metodi helper statici per creare task_continuation_context oggetti:
Usare concurrency::task_continuation_context::use_arbitrary per specificare che la continuazione si esegua su un thread di background.
Usare concurrency::task_continuation_context::use_current per specificare che la continuazione viene eseguita nel thread che ha chiamato
task::then.
È possibile passare un oggetto task_continuation_context al metodo task::then per controllare in modo esplicito il contesto di esecuzione della continuazione oppure passare il task a un altro apartment e quindi chiamare il metodo task::then per controllare in modo implicito il contesto di esecuzione.
Importante
Poiché il thread principale dell'interfaccia utente delle app UWP viene eseguito in STA, le continuazioni create in tale STA vengono eseguite per impostazione predefinita nella STA. Di conseguenza, le continuazioni create nell'MTA vengono eseguite nell'MTA.
La sezione seguente mostra un'app che legge un file dal disco, trova le parole più comuni in tale file e quindi mostra i risultati nell'interfaccia utente. L'operazione finale, l'aggiornamento dell'interfaccia utente, si verifica nel thread dell'interfaccia utente.
Importante
Questo comportamento è specifico per le app UWP. Per le app desktop, non è possibile controllare dove vengono eseguite le continuazioni. L'utilità di pianificazione sceglie invece un thread di lavoro su cui eseguire ogni continuazione.
Importante
Non chiamare concurrency::task::wait nel corpo di una continuazione eseguita su STA. In caso contrario, il runtime genera concurrency::invalid_operation poiché questo metodo blocca il thread corrente e può provocare la mancata risposta da parte dell'app. È tuttavia possibile chiamare il metodo concurrency::task::get per ricevere il risultato dell'attività antecedente in una continuazione basata su attività.
Esempio: controllo dell'esecuzione in un'app Windows Runtime con C++ e XAML
Si consideri un'app XAML C++ che legge un file dal disco, trova le parole più comuni in tale file e quindi mostra i risultati nell'interfaccia utente. Per creare questa app, avviare, in Visual Studio, creando un progetto App vuota (Windows universale) e assegnandogli il CommonWordsnome . Nel manifesto dell'app specificare la funzionalità Raccolta documenti per consentire all'app di accedere alla cartella Documenti. Aggiungere anche il tipo di file Text (.txt) alla sezione delle dichiarazioni del manifesto dell'app. Per altre informazioni sulle funzionalità e le dichiarazioni delle app, vedere Creazione di pacchetti, distribuzione e query di app di Windows.
Aggiornare l'elemento Grid in MainPage.xaml per includere un ProgressRing elemento e un TextBlock elemento. Indica ProgressRing che l'operazione è in corso e TextBlock mostra i risultati del calcolo.
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<ProgressRing x:Name="Progress"/>
<TextBlock x:Name="Results" FontSize="16"/>
</Grid>
Aggiungere le istruzioni seguenti #include a pch.h.
#include <sstream>
#include <ppltasks.h>
#include <concurrent_unordered_map.h>
Aggiungere le dichiarazioni di metodo seguenti alla MainPage classe (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);
Aggiungere le istruzioni seguenti using a MainPage.cpp.
using namespace concurrency;
using namespace std;
using namespace Windows::Storage;
using namespace Windows::Storage::Streams;
In MainPage.cpp, implementare i metodi MainPage::MakeWordList, MainPage::FindCommonWords e MainPage::ShowResults. e MainPage::MakeWordListMainPage::FindCommonWords eseguono operazioni a elevato utilizzo di calcolo. Il MainPage::ShowResults metodo visualizza il risultato del calcolo nell'interfaccia utente.
// 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());
}
Modificare il MainPage costruttore per creare una catena di attività di continuazione che mostri nell'interfaccia utente le parole comuni presenti nell'opera L'Iliade di Omero. Le prime due attività di continuazione, che suddivideno il testo in singole parole e trovano parole comuni, possono richiedere molto tempo e pertanto sono impostate in modo esplicito per l'esecuzione in background. L'attività di continuazione finale, che aggiorna l'interfaccia utente, non specifica alcun contesto di continuazione e quindi segue le regole di threading del modello apartment.
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.
});
}
Annotazioni
In questo esempio viene illustrato come specificare i contesti di esecuzione e come comporre una catena di continuazioni. Tenere presente che, per impostazione predefinita, un'attività creata da un'operazione asincrona esegue le relative continuazioni nell'apartment che ha effettuato la chiamata task::then. Di conseguenza, questo esempio usa task_continuation_context::use_arbitrary per specificare che le operazioni che non coinvolgono l'interfaccia utente devono essere eseguite su un thread in background.
La figura seguente mostra i risultati dell'app CommonWords .
In questo esempio è possibile supportare l'annullamento perché gli task oggetti che supportano create_async usano un token di annullamento implicito. Definisci la tua funzione di lavoro per accettare un oggetto cancellation_token se le tue attività necessitano di rispondere all'annullamento in modo cooperativo. Per altre info sull'annullamento nel PPL, vedi Annullamento nel PPL