Partilhar via


Criando operações assíncronas em C++ para aplicativos UWP

Este documento descreve alguns dos principais pontos a ter em mente quando você usa a classe de tarefa para produzir operações assíncronas baseadas em ThreadPool do Windows em um aplicativo do Tempo de Execução Universal do Windows (UWP).

O uso de programação assíncrona é um componente fundamental no modelo de aplicativo do Tempo de Execução do Windows porque permite que os aplicativos permaneçam responsivos à entrada do usuário. Você pode iniciar uma tarefa de longa execução sem bloquear o thread da interface do usuário e pode receber os resultados da tarefa mais tarde. Você também pode cancelar tarefas e receber notificações de progresso à medida que as tarefas são executadas em segundo plano. O documento Programação assíncrona em C++ fornece uma visão geral do padrão assíncrono disponível no Visual C++ para criar aplicativos UWP. Este documento ensina como consumir e criar cadeias de operações assíncronas do Windows Runtime. Esta seção descreve como usar os tipos em ppltasks.h para produzir operações assíncronas que podem ser consumidas por outro componente do Tempo de Execução do Windows e como controlar como o trabalho assíncrono é executado. Considere também ler Padrões e dicas de programação assíncrona no Hilo (aplicativos da Windows Store em C++ e XAML) para saber como usamos a classe de tarefa para implementar operações assíncronas no Hilo, um aplicativo do Tempo de Execução do Windows em C++ e XAML.

Observação

Você pode usar a Biblioteca de Padrões Paralelos (PPL) e a Biblioteca de Agentes Assíncronos em um aplicativo UWP. No entanto, não é possível usar o Agendador de Tarefas ou o Gerenciador de Recursos. Este documento descreve recursos adicionais que o PPL fornece que estão disponíveis apenas para um aplicativo UWP e não para um aplicativo de área de trabalho.

Pontos principais:

  • Use concurrency::create_async para criar operações assíncronas que podem ser usadas por outros componentes (que podem ser escritos em linguagens diferentes de C++).

  • Use concurrency::progress_reporter para reportar notificações de progresso para componentes que chamam as suas operações assíncronas.

  • Utilize tokens de cancelamento para permitir que operações assíncronas internas sejam canceladas.

  • O comportamento da create_async função depende do tipo de retorno da função de trabalho que é passada para ela. Uma função de trabalho que retorna uma tarefa (ou task<T>task<void>) é executada de forma síncrona no contexto chamado create_async. Uma função de trabalho que retorna T ou void é executada em um contexto arbitrário.

  • Você pode usar o método concurrency::task::then para criar uma cadeia de tarefas que são executadas uma após a outra. Em um aplicativo UWP, o contexto padrão para as continuações de uma tarefa depende de como ela foi construída. Se a tarefa foi criada passando uma ação assíncrona para o construtor da tarefa ou passando uma expressão lambda que retorna uma ação assíncrona, o contexto padrão para todas as continuações dessa tarefa é o contexto atual. Se a tarefa não for construída a partir de uma ação assíncrona, um contexto arbitrário será usado por padrão para as continuações da tarefa. Você pode substituir o contexto padrão pela classe concurrency::task_continuation_context .

Neste documento

Criando operações assíncronas

Você pode usar o modelo de tarefa e continuação na Biblioteca de Padrões Paralelos (PPL) para definir tarefas em segundo plano, bem como tarefas adicionais que são executadas quando a tarefa anterior é concluída. Essa funcionalidade é fornecida pela classe concurrency::task . Para obter mais informações sobre esse modelo e a task classe, consulte Paralelismo de tarefas.

O Tempo de Execução do Windows é uma interface de programação que você pode usar para criar aplicativos UWP que são executados somente em um ambiente de sistema operacional especial. Esses aplicativos usam funções, tipos de dados e dispositivos autorizados e são distribuídos da Microsoft Store. O Tempo de Execução do Windows é representado pela Interface Binária do Aplicativo (ABI). A ABI é um contrato binário subjacente que disponibiliza APIs do Tempo de Execução do Windows para linguagens de programação como Visual C++.

Usando o Tempo de Execução do Windows, você pode usar os melhores recursos de várias linguagens de programação e combiná-los em um único aplicativo. Por exemplo, você pode criar sua interface do usuário em JavaScript e executar a lógica de aplicativo computacionalmente intensiva em um componente C++. A capacidade de executar essas operações computacionais intensivas em segundo plano é um fator-chave para manter sua interface do usuário responsiva. Como a task classe é específica para C++, você deve usar uma interface do Tempo de Execução do Windows para comunicar operações assíncronas a outros componentes (que podem ser escritos em linguagens diferentes de C++). O Tempo de Execução do Windows fornece quatro interfaces que você pode usar para representar operações assíncronas:

Windows::Foundation::IAsyncAction
Representa uma ação assíncrona.

Windows::Foundation::IAsyncActionWithProgress<TProgress>
Representa uma ação assíncrona que relata o progresso.

Windows::Fundação::IAsyncOperation<TResult>
Representa uma operação assíncrona que retorna um resultado.

Windows::Foundation::IAsyncOperationWithProgress<TResult, TProgress>
Representa uma operação assíncrona que retorna um resultado e relata o progresso.

A noção de uma ação significa que a tarefa assíncrona não produz um valor (pense em uma função que retorna void). A noção de uma operação significa que a tarefa assíncrona produz um valor. A noção de progresso significa que a tarefa pode relatar mensagens de progresso ao chamador. JavaScript, o .NET Framework e o Visual C++ fornecem sua própria maneira de criar instâncias dessas interfaces para uso através do limite ABI. Para o Visual C++, o PPL fornece a função concurrency::create_async . Esta função cria uma ação ou operação assíncrona do Tempo de Execução do Windows que representa a conclusão de uma tarefa. A create_async função usa uma função de trabalho (normalmente uma expressão lambda), cria internamente um task objeto e encapsula essa tarefa em uma das quatro interfaces assíncronas do Tempo de Execução do Windows.

Observação

Use create_async somente quando precisar criar funcionalidades que possam ser acessadas de outro idioma ou outro componente do Tempo de Execução do Windows. Use a task classe diretamente quando souber que a operação é produzida e consumida pelo código C++ no mesmo componente.

O tipo de retorno de create_async é determinado pelo tipo de seus argumentos. Por exemplo, se sua função de trabalho não retornar um valor e não relatar o progresso, create_async retornará IAsyncAction. Se sua função de trabalho não retornar um valor e também relatar o progresso, create_async retornará IAsyncActionWithProgress. Para relatar o progresso, forneça um objeto concurrency::progress_reporter como parâmetro para a sua função de trabalho. A capacidade de relatar o progresso permite que você relate a quantidade de trabalho que foi executada e a quantidade que ainda resta (por exemplo, como uma porcentagem). Ele também permite que você relate os resultados à medida que eles ficam disponíveis.

As interfaces IAsyncAction, IAsyncActionWithProgress<TProgress>, IAsyncOperation<TResult> e IAsyncActionOperationWithProgress<TProgress, TProgress> fornecem cada uma um método Cancel que permite cancelar a operação assíncrona. A classe task trabalha com tokens de cancelamento. Quando você usa um token de cancelamento para cancelar o trabalho, o tempo de execução não inicia um novo trabalho que se inscreve nesse token. Os trabalhos que já estão ativos devem monitorar o seu token de cancelamento e parar quando puderem. Este mecanismo é descrito mais detalhadamente no documento Cancelamento no PPL. Você pode conectar o cancelamento de tarefas aos métodos do Tempo de Execução do Windows Cancel de duas maneiras. Primeiro, você pode definir a função de trabalho para passar para create_async um objeto concurrency::cancellation_token. Quando o Cancel método é chamado, esse token de cancelamento é cancelado e as regras normais de cancelamento se aplicam ao objeto subjacente task que suporta a create_async chamada. Se você não fornecer um cancellation_token objeto, o objeto subjacente task definirá um implicitamente. Defina um objeto cancellation_token quando precisar responder cooperativamente ao cancelamento na sua função de trabalho. A seção Exemplo: Controlando a execução em um aplicativo do Tempo de Execução do Windows com C++ e XAML mostra um exemplo de como executar o cancelamento em um aplicativo da Plataforma Universal do Windows (UWP) com C# e XAML que usa um componente C++ personalizado do Tempo de Execução do Windows.

Advertência

Em uma cadeia de continuações de tarefas, sempre limpe o estado e, em seguida, chame concurrency::cancel_current_task quando o token de cancelamento for acionado. Se você retornar cedo em vez de chamar cancel_current_task, a operação transitará para o estado concluído em vez do estado cancelado.

A tabela a seguir resume as combinações que você pode usar para definir operações assíncronas em seu aplicativo.

Para criar essa interface do Tempo de Execução do Windows Retornar este tipo de create_async Passe esses tipos de parâmetros para sua função de trabalho para usar um token de cancelamento implícito Passe esses tipos de parâmetros para sua função de trabalho para usar um token de cancelamento explícito
IAsyncAction void ou task<void> (nenhum) (cancellation_token)
IAsyncActionWithProgress<TProgress> void ou task<void> (progress_reporter) (progress_reporter, cancellation_token)
IAsyncOperation<TResult> T ou task<T> (nenhum) (cancellation_token)
IAsyncActionOperationWithProgress<TProgress, TProgress> T ou task<T> (progress_reporter) (progress_reporter, cancellation_token)

Você pode retornar um valor ou um task objeto da função de trabalho que você passa para a create_async função. Estas variações produzem comportamentos diferentes. Quando você retorna um valor, a função de trabalho é encapsulada em um task para que possa ser executada em um thread em segundo plano. Além disso, o subjacente task usa um token de cancelamento implícito. Por outro lado, se você retornar um task objeto, a função de trabalho será executada de forma síncrona. Portanto, se você retornar um task objeto, certifique-se de que todas as operações longas em sua função de trabalho também sejam executadas como tarefas para permitir que seu aplicativo permaneça responsivo. Além disso, o subjacente task não usa um token de cancelamento implícito. Portanto, precisa definir a sua função de trabalho para aceitar um objeto cancellation_token se precisar de suporte para cancelamento ao retornar um objeto task do create_async.

O exemplo a seguir mostra as várias maneiras de criar um IAsyncAction objeto que pode ser consumido por outro componente do Tempo de Execução do Windows.

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

Exemplo: Criando um componente do Tempo de Execução do Windows em C++ e consumindo-o a partir de C#

Considere um aplicativo que usa XAML e C# para definir a interface do usuário e um componente do Tempo de Execução do Windows em C++ para executar operações de computação intensiva. Neste exemplo, o componente C++ calcula quais números em um determinado intervalo são primos. Para ilustrar as diferenças entre as quatro interfaces de tarefas assíncronas do Tempo de Execução do Windows, comece, no Visual Studio, criando uma Solução em Branco e nomeando-a Primes. Em seguida, adicione à solução um projeto de componente do Tempo de Execução do Windows e nomeie-o .PrimesLibrary Adicione o seguinte código ao arquivo de cabeçalho C++ gerado (este exemplo renomeia Class1.h para Primes.h). Cada public método define uma das quatro interfaces assíncronas. Os métodos que retornam um valor retornam um objeto int< Windows::Foundation::Collections::IVector>. Os métodos que relatam o progresso produzem double valores que definem a porcentagem do trabalho geral que foi concluído.

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

Observação

Por convenção, os nomes de método assíncrono no Tempo de Execução do Windows normalmente terminam com "Async".

Adicione o seguinte código ao arquivo de origem C++ gerado (este exemplo renomeia Class1.cpp para Primes.cpp). A is_prime função determina se sua entrada é prime. Os métodos restantes implementam a Primes classe. Cada chamada para create_async usa uma assinatura compatível com o método a partir do qual é chamada. Por exemplo, porque Primes::ComputePrimesAsync retorna IAsyncAction, a função de trabalho fornecida para create_async não retorna um valor e não usa um progress_reporter objeto como parâmetro.

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

Cada método primeiro executa a validação para garantir que os parâmetros de entrada não sejam negativos. Se um valor de entrada for negativo, o método lançará Platform::InvalidArgumentException. O tratamento de erros é explicado mais adiante nesta seção.

Para consumir esses métodos de um aplicativo UWP, use o modelo Visual C# Blank App (XAML) para adicionar um segundo projeto à solução Visual Studio. Este exemplo nomeia o projeto como Primes. Primeiro, no projeto Primes, adicione uma referência ao projeto PrimesLibrary.

Adicione o seguinte código a MainPage.xaml. Esse código define a interface do usuário para que você possa chamar o componente C++ e exibir resultados.

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

Adicione o seguinte código à MainPage classe em MainPage.xaml. Esse código define um Primes objeto e os manipuladores de eventos button.

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

Esses métodos usam as async palavras-chave e await para atualizar a interface do usuário após a conclusão das operações assíncronas. Para obter informações sobre codificação assíncrona em aplicativos UWP, consulte Threading e programação assíncrona.

Os getPrimesCancellation métodos e cancelGetPrimes trabalham juntos para permitir que o usuário cancele a operação. Quando o usuário escolhe o botão Cancelar , o cancelGetPrimes método chama IAsyncOperationWithProgress<TResult, TProgress>::Cancel para cancelar a operação. O Runtime de Concurrency, que gerencia a operação assíncrona subjacente, lança um tipo de exceção interna que é interceptado pelo Runtime do Windows para comunicar que o cancelamento foi concluído. Para obter mais informações sobre o modelo de cancelamento, consulte Cancelamento.

Importante

Para permitir que o PPL comunique corretamente ao Tempo de Execução do Windows que cancelou a operação, não capture esse tipo de exceção interna. Isso significa que você também não deve pegar todas as exceções (catch (...)). Se você precisar capturar todas as exceções, lance novamente a exceção para garantir que o Tempo de Execução do Windows possa concluir a operação de cancelamento.

A ilustração a seguir mostra o Primes aplicativo após cada opção ter sido escolhida.

Aplicativo Primes do Tempo de Execução do Windows.

Para obter um exemplo que usa create_async para criar tarefas assíncronas que podem ser consumidas por outros idiomas, consulte Usando C++ no exemplo do Bing Maps Trip Optimizer.

Controlando o thread de execução

O Windows Runtime usa o modelo de multithreading COM. Neste modelo, os objetos são hospedados em apartamentos diferentes, dependendo de como eles lidam com sua sincronização. Objetos thread-safe são hospedados no apartamento multi-threaded (MTA). Os objetos que devem ser acessados por um único thread são hospedados em um apartamento de thread único (STA).

Em um aplicativo que tem uma interface do usuário, o thread ASTA (Application STA) é responsável por bombear mensagens de janela e é o único thread no processo que pode atualizar os controles de interface do usuário hospedados pelo STA. Isto tem duas consequências. Primeiro, para permitir que o aplicativo permaneça responsivo, todas as operações com uso intensivo de CPU e E/S não devem ser executadas no thread ASTA. Em segundo lugar, os resultados provenientes de processos de fundo devem ser encaminhados de volta ao ASTA para atualizar a interface do utilizador. No aplicativo UWP em C++, MainPage e outras páginas XAML são todas executadas no ATSA. Portanto, as continuações de tarefas declaradas no ASTA são executadas lá por padrão para que você possa atualizar os controles diretamente no corpo da continuação. No entanto, se se aninhar uma tarefa noutra tarefa, todas as continuações dessa tarefa aninhada serão executadas no MTA. Portanto, você precisa considerar se deve especificar explicitamente em que contexto essas continuações são executadas.

Uma tarefa criada a partir de uma operação assíncrona, como IAsyncOperation<TResult>, usa semântica especial que pode ajudá-lo a ignorar os detalhes de threading. Embora uma operação possa ser executada num thread em segundo plano (ou possa não ser apoiada por um thread), as suas continuações têm por padrão a garantia de serem executadas no apartamento que iniciou as operações de continuação, ou seja, no apartamento que chamou task::then. Você pode usar a classe concurrency::task_continuation_context para controlar o contexto de execução de uma continuação. Use estes métodos auxiliares estáticos para criar task_continuation_context objetos:

Você pode passar um task_continuation_context objeto para o método task::then para controlar explicitamente o contexto de execução da continuação ou pode passar a tarefa para outro apartamento e, em seguida, chamar o task::then método para controlar implicitamente o contexto de execução.

Importante

Como o thread principal da interface do usuário dos aplicativos UWP é executado em STA, as continuações que você cria nesse STA por padrão são executadas no STA. Assim, as continuações que você cria no MTA são executadas no MTA.

A seção a seguir mostra um aplicativo que lê um arquivo do disco, localiza as palavras mais comuns nesse arquivo e mostra os resultados na interface do usuário. A operação final, que é atualizar a IU, ocorre no thread da IU.

Importante

Esse comportamento é específico para aplicativos UWP. Para aplicativos da área de trabalho, você não controla onde as continuações são executadas. Em vez disso, o agendador escolhe um thread de trabalho no qual executar cada continuação.

Importante

Não chame concorrência::task::wait no corpo de uma continuação que é executada no STA. Caso contrário, o runtime lança concurrency::invalid_operation porque este método bloqueia o thread atual e pode fazer com que a aplicação deixe de responder. No entanto, é possível chamar o método concurrency::task::get para obter o resultado da tarefa antecedente numa continuidade baseada em tarefas.

Exemplo: Controlando a execução em um aplicativo do Tempo de Execução do Windows com C++ e XAML

Considere um aplicativo XAML C++ que lê um arquivo do disco, localiza as palavras mais comuns nesse arquivo e mostra os resultados na interface do usuário. Para criar este aplicativo, comece, no Visual Studio, criando um projeto Aplicativo em Branco (Universal Windows) e nomeando-o CommonWords. No manifesto do aplicativo, especifique o recurso Biblioteca de Documentos para permitir que o aplicativo acesse a pasta Documentos. Adicione também o tipo de arquivo Texto (.txt) à seção de declarações do manifesto do aplicativo. Para obter mais informações sobre recursos e declarações de aplicativos, consulte Empacotamento, implantação e consulta de aplicativos do Windows.

Atualize o Grid elemento em MainPage.xaml para incluir um ProgressRing elemento e um TextBlock elemento . O ProgressRing indica que a operação está em andamento e mostra os TextBlock resultados do cálculo.

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

Adicione as seguintes #include instruções a pch.h.

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

Adicione as seguintes declarações de método à 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);

Adicione as seguintes using instruções a MainPage.cpp.

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

Em MainPage.cpp, implemente os MainPage::MakeWordListmétodos , MainPage::FindCommonWordse MainPage::ShowResults . O MainPage::MakeWordList e MainPage::FindCommonWords executar operações computacionais intensivas. O MainPage::ShowResults método exibe o resultado do cálculo na interface do usuário.

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

Modifique o MainPage construtor para criar uma cadeia de tarefas de continuação que exiba na interface do usuário as palavras comuns no livro A Ilíada de Homero. As duas primeiras tarefas de continuação, que dividem o texto em palavras individuais e encontram palavras comuns, podem ser demoradas e, portanto, são explicitamente definidas para serem executadas em segundo plano. A tarefa de continuação final, que atualiza a interface do usuário, não especifica nenhum contexto de continuação e, portanto, segue as regras de threading do apartamento.

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

Observação

Este exemplo demonstra como especificar contextos de execução e como compor uma cadeia de continuações. Lembre-se de que, por padrão, uma tarefa criada a partir de uma operação assíncrona executa suas continuações no apartamento chamado task::then. Portanto, este exemplo usa task_continuation_context::use_arbitrary para especificar que as operações que não envolvem a interface do usuário sejam executadas em um thread em segundo plano.

A ilustração a seguir mostra os resultados do CommonWords aplicativo.

Aplicação CommonWords do Tempo de Execução do Windows.

Neste exemplo, é possível oferecer suporte ao cancelamento porque os task objetos que suportam create_async usam um token de cancelamento implícito. Defina a função de trabalho para aceitar um objeto cancellation_token caso as suas tarefas precisem responder ao cancelamento do processo de forma cooperativa. Para mais informações sobre cancelamento no PPL, consulte Cancelamento no PPL

Ver também

Runtime de Concorrência