Creazione di operazioni asincrone in C++ per app UWP

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à di lunga durata senza bloccare il thread dell'interfaccia utente e ricevere i risultati dell'attività in un secondo momento. È anche possibile annullare attività e ricevere notifiche di stato come attività in esecuzione 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.

Nota

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 Gestione 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 permettere l'annullamento delle operazioni asincrone interne.

  • Il comportamento della funzione create_async dipende dal tipo restituito della funzione lavoro che viene passata ad essa. Una funzione lavoro che restituisce un'attività ( task<T> o task<void>) viene eseguita in modo sincrono nel contesto che ha chiamato create_async. Una funzione lavoro che restituisce T o void viene eseguita in un contesto arbitrario.

  • È possibile usare il metodo concurrency::task::then per creare una catena di attività 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 di attività oppure passando un'espressione lambda che restituisce un'azione asincrona, il contesto predefinito per tutte le continuazioni dell'attività sarà 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 il modello di attività e continuazione nella libreria PPL (Parallel Patterns Library) per definire le attività in background, oltre ad attività aggiuntive eseguite al completamento dell'attività precedente. Questa funzionalità viene fornita dalla classe concurrency::task . Per altre informazioni su questo modello e sulla classe task , vedere Task Parallelism.

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 app che richiede attività di calcolo complesse in un componente C++. La possibilità di eseguire queste operazioni che richiedono attività di calcolo complesse in background è un fattore essenziale 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.

Il concetto di azione indica che un'attività asincrona non produce alcun valore, analogamente a una funzione che restituisce void. Il concetto di operazione indica che l'attività asincrona produce un valore. Il concetto di stato indica che l'attività può inviare messaggi di stato al chiamante. JavaScript, .NET Framework e Visual C++ offrono un modo specifico per creare istanze di queste interfacce da usare oltre i limiti dell'ABI. Per Visual C++ la libreria 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.

Nota

Usare create_async solo quando è necessario creare funzionalità accessibili da un altro linguaggio o da un altro componente di Windows Runtime. Usare direttamente la classe task quando si è certi che l'operazione viene prodotta e utilizzata da codice C++ nello stesso componente.

Il tipo restituito di create_async viene determinato dal tipo dei rispettivi argomenti. Se, ad esempio, la funzione lavoro non restituisce alcun valore e non segnala lo stato, create_async restituirà IAsyncAction. Se la funzione lavoro non restituisce alcun valore ma segnala lo stato, create_async restituirà IAsyncActionWithProgress. Per segnalare lo stato, fornire un oggetto concurrency::progress_reporter come parametro per la funzione lavoro. La possibilità di segnalare lo stato permette di segnalare la quantità di lavoro eseguita e la quantità rimanente, ad esempio sotto forma di percentuale. Permette anche di segnalare i risultati non appena disponibili.

Le interfacce IAsyncAction, IAsyncActionWithProgress<TProgress>, IAsyncOperation<TResult>e IAsyncActionOperationWithProgress<TProgress, TProgress> forniscono un metodo Cancel che permette di annullare l'operazione asincrona. La classe task può essere usata con i token di annullamento. Quando si usa un token di annullamento per annullare il lavoro, il runtime non avvia nuovo lavoro che sottoscrive tale token. Il lavoro già attivo è in grado di monitorare l'annullamento e arrestarsi quando possibile. Questo meccanismo è descritto in modo più dettagliato nel documento Cancellation in the PPL. Puoi connettere l'annullamento delle attività con i metodi di Windows Runtime Cancel in due modi. È prima di tutto possibile definire la funzione lavoro passata 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 oggetto cancellation_token , l'oggetto task sottostante ne definirà uno implicitamente. Definire un oggetto cancellation_token quando è necessario rispondere in modo cooperativo all'annullamento nella funzione 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 (piattaforma UWP (Universal Windows Platform)) con C# e XAML che usa un componente C++ di Windows Runtime personalizzato.

Avviso

In una catena di continuazioni di attività, pulire sempre lo stato e quindi chiamare concurrency::cancel_current_task quando il token di annullamento viene annullato. Se si esce prima di chiamare cancel_current_task, l'operazione passa allo stato completato invece che allo stato annullato.

La tabella seguente riepiloga le combinazioni che possono essere usate per definire operazioni asincrone nell'app.

Per creare questa interfaccia di Windows Runtime Restituire questo tipo da create_async Passare questi tipi di parametro alla funzione lavoro per usare implicitamente il token di annullamento Passare questi tipi di parametro alla funzione lavoro per usare esplicitamente il token di annullamento
IAsyncAction void oppure task<void> (nessuno) (cancellation_token)
IAsyncActionWithProgress<TProgress> void oppure task<void> (progress_reporter) (progress_reporter, cancellation_token)
IAsyncOperation<TResult> T oppure task<T> (nessuno) (cancellation_token)
IAsyncActionOperationWithProgress<TProgress, TProgress> T oppure task<T> (progress_reporter) (progress_reporter, cancellation_token)

È possibile restituire un valore o un oggetto task dalla funzione lavoro passata alla funzione create_async . Queste variazioni producono comportamenti diversi. Quando viene restituito un valore, la funzione lavoro viene sottoposta a wrapping in task in modo che possa essere eseguita su una thread in background. L'oggetto task sottostante usa inoltre un token di annullamento implicito. Se invece viene restituito un oggetto task , la funzione lavoro viene eseguita in modo sincrono. Se quindi viene restituito un oggetto task , assicurarsi che tutte le operazioni lunghe nella funzione lavoro possano essere eseguite come attività per mantenere la reattività dell'applicazione. L'oggetto task sottostante inoltre non usa un token di annullamento implicito. È quindi necessario definire la funzione lavoro in modo che accetti un oggetto cancellation_token se è necessario il supporto per l'annullamento quando si restituisce un oggetto task 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: Creazione di un componente Windows Runtime C++ e utilizzo 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 i numeri primi di un intervallo specifico. 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 un progetto Componente Windows Runtime alla soluzione e denominarlo PrimesLibrary. Aggiungere il codice seguente al file di intestazione C++ generato. Questo esempio rinomina Class1.h in Primes.h. Ogni metodo public 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 producono valori double che definiscono la percentuale di lavoro complessivo completata.

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

Nota

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. Questo esempio rinomina Class1.cpp in Primes.cpp. La funzione is_prime determina se l'input è un numero primo. I metodi rimanenti implementano la classe Primes . Ogni chiamata a create_async usa una firma compatibile con il metodo da cui viene effettuata la chiamata. Ad esempio, poiché Primes::ComputePrimesAsync restituisce IAsyncAction, la funzione lavoro fornita a create_async non restituisce alcun valore e non accetta alcun oggetto progress_reporter 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 un'eccezione 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. Dal progetto Primes aggiungere un riferimento al progetto PrimesLibrary .

Aggiungere il codice seguente a MainPage.xaml. Questo codice definisce l'interfaccia utente, in modo da permettere la chiamata del componente C++ e la visualizzazione dei 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 classe MainPage in MainPage.xaml. Questo codice definisce un oggetto Primes 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 parole chiave async e await per aggiornare l'interfaccia utente dopo il completamento delle operazioni asincrone. Per informazioni sulla codifica asincrona nelle app UWP, vedi Threading e programmazione asincrona.

I metodi getPrimesCancellation e cancelGetPrimes interagiscono per permettere 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 runtime di concorrenza, che gestisce l'operazione asincrona sottostante, genera un tipo di eccezione interno intercettato da 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 è necessario non 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 che ogni opzione è stata selezionata.

Windows Runtime Primes app.

Per un esempio che usa create_async per creare attività asincrone che possono essere utilizzate da altri linguaggi, vedere Uso di C++ nell'esempio di Bing Mappe Trip Optimizer.

Controllo del thread di esecuzione

Windows Runtime usa il modello di threading COM. In questo modello gli oggetti vengono ospitati in diversi apartment, in base al modo in cui gestiscono la rispettiva sincronizzazione. Gli oggetti thread-safe vengono ospitati nell'apartment a thread multipli (MTA, Multi-Threaded Apartment). Gli oggetti a cui deve accedere un singolo thread vengono ospitati in un apartment a thread singolo (STA, Single-Threaded Apartment).

In un'app che ha un'interfaccia utente, il thread ASTA (Application STA) è responsabile per la distribuzione dei messaggi della finestra ed è l'unico thread nel processo in grado di aggiornare i controlli dell'interfaccia utente ospitati nell'apartment a thread singolo. Questo comportamento ha due conseguenze. Per abilitare l'app in modo che rimanga reattiva, è prima di tutto necessario che tutte le operazioni a utilizzo elevato di CPU e le operazioni I/O non vengano eseguite nel thread ASTA. È quindi necessario eseguire di nuovo il marshaling dei thread di background nel thread ASTA per aggiornare l'interfaccia utente. In un'app MainPage UWP C++ e in altre pagine XAML vengono eseguite in ATSA. Le continuazioni delle attività dichiarate nel thread ASTA vengono quindi eseguite in tale thread per impostazione predefinita. Sarà quindi possibile aggiornare i controlli direttamente nel corpo della continuazione. Se tuttavia si annida un'attività in un'altra attività, eventuali continuazioni in tale attività annidata verranno eseguite nell'apartment a thread multipli. È quindi necessario valutare se specificare in modo esplicito il contesto in cui devono essere eseguite le continuazioni.

Un'attività creata da un'operazione asincrona, ad esempio IAsyncOperation<TResult>, usa una semantica speciale che permette di ignorare i dettagli relativi al threading. Anche se è possibile che un'operazione venga eseguita in un thread in background o che non sia supportata da alcun thread, per impostazione predefinita le rispettive continuazioni verranno eseguite nell'apartment che ha avviato le operazioni di continuazione, ovvero l'apartment 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 di helper statici per creare oggetti task_continuation_context :

È possibile passare un oggetto task_continuation_context al metodo task::then per controllare in modo esplicito il contesto di esecuzione della continuazione oppure passare l'attività 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 sta. Le continuazioni create nell'apartment a thread multipli verranno di conseguenza eseguite nell'apartment a thread multipli.

La sezione seguente illustra un'app che legge un file da disco, individua 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 la posizione in cui vengono eseguite le continuazioni. L'Utilità di pianificazione sceglie invece un thread di lavoro in cui eseguire ogni continuazione.

Importante

Non chiamare concurrency::task::wait nel corpo di una continuazione che viene eseguita nell'apartment a thread singolo. 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 da disco, individua 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 capacità Raccolta documenti per permettere all'app di accedere alla cartella Documenti. Aggiungere anche il tipo di file di testo (con estensione txt) alla sezione relativa alle dichiarazioni nel 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 elemento ProgressRing e un elemento TextBlock . L'elemento ProgressRing indica 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 del metodo seguente alla classe MainPage (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 using seguenti 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::FindCommonWordse MainPage::ShowResults . MainPage::MakeWordList e MainPage::FindCommonWords eseguono operazioni a utilizzo elevato di calcolo. Il metodo MainPage::ShowResults 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 costruttore MainPage per creare una catena di attività di continuazione che visualizza nell'interfaccia utente le parole comuni rilevate nel libro Iliade di Omero. Le prime due attività di continuazione, che suddividono il testo in singole parole e trovano le parole comuni, possono richiedere molto tempo e sono quindi configurate 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 dell'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.
    });
}

Nota

Questo esempio illustra come specificare i contesti di esecuzione e come creare una catena di continuazioni. È importante ricordare che per impostazione predefinita un'attività creata da un'operazione asincrona esegue le rispettive continuazione nell'apartment che ha chiamato task::then. Questo esempio usa quindi task_continuation_context::use_arbitrary per specificare che le operazioni che non coinvolgono l'interfaccia utente vengano eseguite in un thread in background.

La figura seguente mostra i risultati dell'app CommonWords .

Windows Runtime CommonWords app.

In questo esempio è possibile supportare l'annullamento perché gli task oggetti che supportano create_async usano un token di annullamento implicito. Definire la funzione lavoro in modo che accetti un oggetto cancellation_token se l'attività deve rispondere all'annullamento in modo cooperativo. Per altre informazioni sull'annullamento nella libreria PPL, vedere Cancellation in the PPL.

Vedi anche

Runtime di concorrenza