Creación de operaciones asincrónicas en C++ para aplicaciones de UWP
En este documento se describen algunos de los puntos clave a tener en cuenta al usar la clase de tarea para generar operaciones asincrónicas basadas en subprocesos de Windows en una aplicación universal de Windows Runtime (UWP).
El uso de la programación asincrónica es un componente clave en el modelo de aplicaciones de Windows Runtime porque permite que las aplicaciones sigan respondiendo a los datos proporcionados por el usuario. Puede iniciar una tarea de ejecución prolongada sin bloquear el subproceso de la interfaz de usuario, y puede recibir los resultados de la tarea más adelante. También puede cancelar tareas y recibir notificaciones de progreso como tareas ejecutadas en segundo plano. En el documento Programación asincrónica en C++ se proporciona información general sobre el patrón asincrónico que está disponible en Visual C++ para crear aplicaciones para UWP. En ese documento se enseña cómo utilizar y crear cadenas de operaciones asincrónicas de Windows Runtime. En esta sección se describe cómo usar los tipos de ppltasks.h para generar operaciones asincrónicas que otro componente de Windows Runtime puede consumir y cómo controlar la ejecución del trabajo asincrónico. También puede leer la página Patrones y sugerencias de programación asincrónica en Hilo (aplicaciones de la Tienda Windows que usan C++ y XAML) para obtener información sobre cómo se usa la clase de tarea para implementar operaciones asincrónicas en Hilo, una aplicación de Windows Runtime que usa C++ y XAML.
Nota:
Puede usar la Biblioteca de patrones de procesamiento paralelo (PPL) y la Biblioteca de agentes asincrónicos en una aplicación para UWP. Sin embargo, no puede utilizar el Programador de tareas o el Administrador de recursos. En este documento se describen las características adicionales que proporciona la PPL y que solo están disponibles para una aplicación para UWP, no para una aplicación de escritorio.
Puntos clave
Utilice concurrency::create_async para crear las operaciones asincrónicas que otros componentes pueden utilizar (que pueden estar escritas en lenguajes diferentes de C++).
Utilice concurrency::progress_reporter para informar de notificaciones de progreso a los componentes que llaman a las operaciones asincrónicas.
Utilice los tokens de cancelación para permitir que las operaciones asincrónicas internas se cancelen.
El comportamiento de la función
create_async
depende del tipo de valor devuelto de la función de trabajo a la que se pasa. Una función de trabajo que devuelve una tarea (task<T>
otask<void>
) se ejecuta de manera sincrónica en el contexto que llamó acreate_async
. Una función de trabajo que devuelveT
ovoid
se ejecuta en un contexto arbitrario.Puede utilizar el método concurrency::task::then para crear una cadena de tareas que se ejecutan una tras otra. En una aplicación para UWP, el contexto predeterminado para las continuaciones de una tarea depende de cómo se creó esa tarea. Si la tarea se creó pasando una acción asincrónica al constructor de tarea, o pasando una expresión lambda que devuelve una acción asincrónica, el contexto predeterminado para todas las continuaciones de esa tarea es el contexto actual. Si la tarea no se creó a partir de una acción asincrónica, de forma predeterminada un contexto arbitrario se utiliza para las continuaciones de la tarea. Puede reemplazar el contexto predeterminado con la clase concurrency::task_continuation_context .
En este documento
Crear operaciones asincrónicas
Puede utilizar la tarea y el modelo de continuación en la biblioteca de patrones de procesamiento paralelo (PPL) para definir tareas en segundo plano, así como tareas adicionales que se ejecutan cuando la tarea anterior se completa. La clase concurrency::task proporciona esta funcionalidad. Para obtener más información sobre este modelo y la clase task
, consulte Task Parallelism.
Windows Runtime es una interfaz de programación que puedes usar para crear aplicaciones para UWP que se ejecutan solo en un entorno de especial del sistema operativo. Esas aplicaciones usan funciones, tipos de datos y dispositivos autorizados, y se distribuyen desde la tienda Microsoft Store. Windows Runtime está representado mediante la interfaz binaria de aplicaciones Application Binary Interface (ABI). La ABI es un contrato binario subyacente que pone las API de Windows Runtime a disposición de los lenguajes de programación, como por ejemplo Visual C++.
Mediante Windows Runtime, puede usar las mejores características de lenguajes de programación diferentes y combinarlos en una aplicación. Por ejemplo, podría crear una interfaz de usuario en JavaScript y ejecutar la lógica de la aplicación que requiere gran cantidad de recursos de computación en un componente de C++. La capacidad de realizar estas operaciones que requieren gran cantidad de recursos de computación en segundo plano es un factor clave para que la interfaz de usuario siga respondiendo. Dado que la clase task
es específica de C++, debe utilizar una interfaz de Windows Runtime para comunicar operaciones asincrónicas a otros componentes (que pueden estar escritos en lenguajes distintos de C++). Windows Runtime proporciona cuatro interfaces que se pueden utilizar para representar operaciones asincrónicas:
Windows::Foundation::IAsyncAction
Representa una acción asincrónica.
Windows::Foundation::IAsyncActionWithProgress<TProgress>
Representa una acción asincrónica que informa sobre el progreso.
Windows::Foundation::IAsyncOperation<TResult>
Representa una operación asincrónica que devuelve un resultado.
Windows::Foundation::IAsyncOperationWithProgress<TResult, TProgress>
Representa una operación asincrónica que devuelve un resultado e informa sobre el progreso.
El concepto de una acción significa que la tarea asincrónica no genera un valor (piense en una función que devuelve void
). El concepto de una operación significa que la tarea asincrónica genera un valor. El concepto de progreso significa que la tarea puede informar sobre mensajes de progreso al llamador. JavaScript, .NET Framework y Visual C++ proporcionan su propia manera de crear instancias de estas interfaces para su uso a través del límite de ABI. Para Visual C++, la PPL proporciona la función concurrency::create_async . Esta función crea una acción u operación asincrónica de Windows Runtime que representan la finalización de una tarea. La función create_async
toma una función de trabajo (normalmente una expresión lambda), crea internamente un objeto task
y ajusta esa tarea en una de las cuatro interfaces asincrónicas de Windows Runtime.
Nota:
Use create_async
únicamente si tiene que crear una funcionalidad a la que se puede tener acceso desde otro lenguaje u otro componente de Windows Runtime. Utilice la clase task
directamente cuando conozca que el código de C++ genera y consume la operación en el mismo componente.
El tipo de valor devuelto create_async
viene determinado por el tipo de sus argumentos. Por ejemplo, si la función de trabajo no devuelve un valor y no informa sobre el progreso, create_async
devuelve IAsyncAction
. Si la función de trabajo no devuelve un valor pero informa sobre el progreso, create_async
devuelve IAsyncActionWithProgress
. Para informar sobre el progreso, proporcione un objeto concurrency::progress_reporter como parámetro para la función de trabajo. La capacidad para informar sobre el progreso permite indicar qué cantidad de trabajo se realizó y qué cantidad todavía permanece sin realizar (por ejemplo, como un porcentaje). Permite también informar sobre los resultados cuando están disponibles.
Cada una de las interfaces IAsyncAction
, IAsyncActionWithProgress<TProgress>
, IAsyncOperation<TResult>
y IAsyncActionOperationWithProgress<TProgress, TProgress>
proporcionan un método Cancel
que permite cancelar la operación asincrónica. La clase task
funciona con tokens de cancelación. Cuando usa un token de cancelación para cancelar el trabajo, el runtime no inicia el nuevo trabajo suscrito a dicho token. El trabajo que ya está activo puede supervisar su token de cancelación y detenerse cuando puede. Este mecanismo se describe con mayor detalle en el documento Cancellation in the PPL. Puede conectar la cancelación de tareas con los métodos Cancel
de Windows Runtime de dos maneras. Primero, puede definir la función de trabajo que pasa a create_async
para que tome un objeto concurrency::cancellation_token . Cuando se llama al método Cancel
, este token de cancelación se cancela y las reglas habituales de cancelación se aplican al objeto subyacente task
que admite la llamada create_async
. Si no se proporciona un objeto cancellation_token
, el objeto subyacente task
define uno implícitamente. Después, defina un objeto cancellation_token
cuando necesite responder de forma cooperativa a la cancelación en su función de trabajo. La sección Ejemplo: controlar la ejecución en una aplicación de la Tienda Windows con C++ y XAML muestra un ejemplo de cómo realizar la cancelación en una aplicación de la plataforma universal de Windows (UWP) con C# y XAML que usa un componente personalizado de Windows Runtime de C++.
Advertencia
En una cadena de continuaciones de tareas, limpie siempre el estado y llame a concurrency::cancel_current_task cuando se cancele el token de cancelación. Si vuelve antes en lugar de llamar a cancel_current_task
, la operación evoluciona al estado completado en lugar del estado cancelado.
En la tabla siguiente se resumen las combinaciones que puede utilizar para definir operaciones asincrónicas en la aplicación.
Para crear esta interfaz de Windows Runtime | Devuelve este tipo a partir de create_async |
Pase estos tipos de parámetro a la función de trabajo para utilizar un token de cancelación implícito | Pase estos tipos de parámetro a la función de trabajo para utilizar un token de cancelación explícito |
---|---|---|---|
IAsyncAction |
void o task<void> |
(ninguno) | (cancellation_token ) |
IAsyncActionWithProgress<TProgress> |
void o task<void> |
(progress_reporter ) |
(progress_reporter , cancellation_token ) |
IAsyncOperation<TResult> |
T o task<T> |
(ninguno) | (cancellation_token ) |
IAsyncActionOperationWithProgress<TProgress, TProgress> |
T o task<T> |
(progress_reporter ) |
(progress_reporter , cancellation_token ) |
Puede devolver un valor o un objeto task
de la función de trabajo que se pasa a la función create_async
. Estas variaciones representan distintos comportamientos. Cuando devuelve un valor, la función de trabajo se ajusta a la clase task
para poder ejecutarse en un subproceso en segundo plano. Además, la clase task
subyacente utiliza un token de cancelación implícito. Por el contrario, si devuelve un objeto task
, la función de trabajo se ejecuta sincrónicamente. Por consiguiente, si devuelve un objeto task
, asegúrese de que cualquier operación larga en la función de trabajo también se ejecuta como tarea para permitir que la aplicación siga respondiendo. Además, la clase task
subyacente no utiliza un token de cancelación implícito. Por consiguiente, debe definir la función de trabajo para tomar un objeto cancellation_token
si necesita compatibilidad con la cancelación cuando devuelva un objeto task
de create_async
.
En el ejemplo siguiente se muestran las distintas maneras de crear un objeto IAsyncAction
que otro componente de Windows Runtime puede consumir.
// 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.
});
});
Ejemplo: Crear un componente de Windows Runtime de C++ y consumirlo desde C#
Piense en una aplicación que utiliza XAML y C# para definir la interfaz de usuario, y un componente de Windows Runtime de C++ para realizar operaciones de cálculo intensivo. En este ejemplo, el componente de C++ calcula que los números de un intervalo dado son primos. Para mostrar las diferencias entre las cuatro interfaces de tarea asincrónica de Windows Runtime, comience, en Visual Studio, creando una Solución en blanco y asignándole el nombre Primes
. Luego, agregue a la solución un proyecto Componente de Windows Runtime y asígnele el nombre PrimesLibrary
. Agregue el código siguiente al archivo de encabezado de C++ generado (este ejemplo cambia el nombre de Class1.h a Primes.h). Cada método public
define una de las cuatro interfaces asincrónicas. Los métodos que devuelven un valor devuelven un objeto Windows::Foundation::Collections::IVector<int>. Los métodos que informan sobre el progreso proporcionan valores double
que definen el porcentaje del trabajo total completado.
#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:
Por convención, los nombres de método asincrónico en Windows Runtime normalmente finalizan con "Async".
Agregue el código siguiente al archivo de origen de C++ generado (este ejemplo cambia el nombre de Class1.cpp a Primes.cpp). La función is_prime
determina si la entrada es número primo. Los métodos restantes implementan la clase Primes
. Cada llamada a create_async
utiliza una signatura compatible con el método desde el que se llama. Por ejemplo, puesto que Primes::ComputePrimesAsync
devuelve IAsyncAction
, la función de trabajo que se proporciona a create_async
no devuelve un valor y no toma un objeto progress_reporter
como su 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 realiza primero la validación para garantizar que los parámetros de entrada no son negativos. Si un valor de entrada es negativo, el método produce Platform::InvalidArgumentException. El control de errores se explica más adelante en esta sección.
Para utilizar estos métodos de una aplicación para UWP, use la plantilla Aplicación vacía (XAML) de Visual C# para agregar un segundo proyecto a la solución de Visual Studio. En este ejemplo, se asigna al proyecto el nombre Primes
. Luego, desde el proyecto Primes
, agregue una referencia al proyecto PrimesLibrary
.
Agregue el código siguiente a MainPage.xaml. Este código define la interfaz de usuario de modo que se puede llamar al componente de C++ y mostrar los 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>
Agregue el código siguiente a la clase MainPage
en MainPage.xaml. Este código define un objeto Primes
y los controladores de eventos de botón.
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();
}
Estos métodos utilizan las palabras clave async
y await
para actualizar la interfaz de usuario después de que las operaciones asincrónicas finalicen. Para obtener información sobre la codificación asincrónica en aplicaciones para UWP, consulte Subprocesos y programación asincrónica.
Los métodos getPrimesCancellation
y cancelGetPrimes
colaboran para permitir al usuario cancelar la operación. Cuando el usuario elige el botón Cancelar, el método cancelGetPrimes
llama a IAsyncOperationWithProgress<TResult, TProgress>::Cancel para cancelar la operación. El Runtime de simultaneidad, que administra la operación asincrónica subyacente, produce un tipo de excepción interna detectado por Windows Runtime para comunicar que la cancelación ha finalizado. Para obtener más información sobre el modelo de cancelación, consulte Cancelación.
Importante
Para permitir que la PPL informe correctamente a Windows Runtime de que se ha cancelado la operación, no detecte este tipo de excepción interna. Esto significa que tampoco debe detectar todas las excepciones (catch (...)
). Si tienen que detectar todas las excepciones, vuelva a producir la excepción para garantizar que Windows Runtime puede completar la operación de cancelación.
En la ilustración siguiente se muestra la aplicación Primes
una vez que se ha elegido cada opción.
Para ver ejemplos en los que se usa create_async
para crear tareas asincrónicas que se pueden usar en otros lenguajes, consulte Usar C++ en el ejemplo de Optimizador de recorridos de Mapas de Bing.
Controlar el subproceso de ejecución
Windows Runtime usa el modelo de subprocesos COM. En este modelo, los objetos se hospedan en contenedores diferentes, dependiendo de cómo controlan la sincronización. Los objetos seguros para subprocesos se hospedan en el contenedor multiproceso (MTA). Los objetos a los que debe tener acceso un único subproceso se hospedan en un contenedor uniproceso (STA).
En una aplicación con una interfaz de usuario, el subproceso ASTA (Application STA, Aplicación STA) es responsable de suministrar mensajes de Windows y es el único subproceso del proceso que puede actualizar los controles de la interfaz de usuarios hospedados en STA. Esto tiene dos consecuencias. En primer lugar, para permitir que la aplicación siga respondiendo, ninguna de las operaciones que consumen muchos recursos de la CPU ni las operaciones de E/S deben ejecutarse en el subproceso ASTA. En segundo lugar, los resultados procedentes de los subprocesos en segundo plano deben calcularse de nuevo hacia el ASTA para actualizar la interfaz de usuario. En la aplicación para UWP de C++, MainPage
y otras páginas XAML se ejecutan todas en el ATSA. Por consiguiente, las continuaciones de tareas que se declaran en el ASTA se ejecutan allí de forma predeterminada, de modo que se pueden actualizar los controles directamente en el cuerpo de continuación. Sin embargo, si se anida una tarea en otra tarea, cualquier continuación en esa tarea anidada se ejecuta en el MTA. Por consiguiente, deberá considerar si especificar explícitamente en qué contexto se ejecutan estas continuaciones.
Una tarea que se crea a partir de una operación asincrónica, como IAsyncOperation<TResult>
, usa la semántica especial que puede ayudar a omitir los detalles de subprocesos. Aunque una operación puede ejecutarse en un subproceso en segundo plano (o puede que no esté respaldada por un subproceso en absoluto), sus continuaciones se garantizan de forma predeterminada para que se ejecuten en el contenedor que inició las operaciones de continuación (es decir, desde el contenedor que llamó a task::then
). Puede utilizar la clase concurrency::task_continuation_context para controlar el contexto de ejecución de una continuación. Utilice estos métodos del asistente estáticos para crear objetos task_continuation_context
:
Utilice concurrency::task_continuation_context::use_arbitrary para especificar que la continuación se ejecuta en un subproceso en segundo plano.
Utilice concurrency::task_continuation_context::use_current para especificar que la continuación se ejecuta en el subproceso que llamó a
task::then
.
Puede pasar un objeto task_continuation_context
al método task::then para controlar explícitamente el contexto de ejecución de la continuación o puede pasar la tarea a otro contenedor y después llamar al método task::then
para controlar implícitamente el contexto de ejecución.
Importante
Dado que el subproceso de la interfaz de usuario principal de las aplicaciones para UWP se ejecutan bajo el STA, las continuaciones que se crean en ese STA se ejecutan en el STA de forma predeterminada. En consecuencia, las continuaciones que se crean en el MTA ejecutan en el MTA.
En la siguiente sección se muestra una aplicación que lee un archivo desde el disco, encuentra la mayoría de las palabras comunes en ese archivo y, a continuación, muestra los resultados en la interfaz de usuario. La operación final, actualizar la interfaz de usuario, se produce en el subproceso de la interfaz de usuario.
Importante
Este comportamiento es específico de las aplicaciones para UWP. En las aplicaciones de escritorio, no se controla donde se ejecutan las continuaciones. En su lugar, el programador elige un subproceso de trabajo en el que ejecutar cada continuación.
Importante
No llame a concurrency::task::wait en el cuerpo de una continuación que se ejecuta en el STA. De lo contrario, el runtime produce concurrency::invalid_operation porque este método bloquea el subproceso actual y pueden provocar que la aplicación no responda. Sin embargo, puede llamar al método concurrency::task::get para recibir el resultado de la tarea anterior en una continuación basada en tareas.
Ejemplo: controlar la ejecución en una aplicación Windows Runtime con C++ y XAML
Piense en una aplicación XAML de C++ que lee un archivo desde el disco, encuentra la mayoría de las palabras comunes en ese archivo y, a continuación, muestran los resultados en la interfaz de usuario. Para crear esta aplicación, en Visual Studio, comience creando un proyecto Aplicación vacía (Windows universal) y asígnele el nombre CommonWords
. En el manifiesto de la aplicación, especifique la capacidad de la Biblioteca de documentos para permitir que la aplicación obtenga acceso a la carpeta de documentos. Agregue también el tipo de archivo de texto (.txt) a la sección de declaraciones del manifiesto de la aplicación. Para obtener más información sobre las funcionalidades y declaraciones de aplicaciones, consulte Empaquetado, implementación y consulta de aplicaciones de Windows.
Actualice el elemento Grid
en MainPage.xaml para incluir un elemento ProgressRing
y un elemento TextBlock
. ProgressRing
indica que la operación está en curso y TextBlock
muestra los resultados del cálculo.
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<ProgressRing x:Name="Progress"/>
<TextBlock x:Name="Results" FontSize="16"/>
</Grid>
Agregue las instrucciones #include
siguientes a pch.h.
#include <sstream>
#include <ppltasks.h>
#include <concurrent_unordered_map.h>
Agregue las declaraciones de método siguientes a la clase 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);
Agregue las siguientes instrucciones using
a MainPage.cpp.
using namespace concurrency;
using namespace std;
using namespace Windows::Storage;
using namespace Windows::Storage::Streams;
En MainPage.cpp, implemente los métodos MainPage::MakeWordList
, MainPage::FindCommonWords
y MainPage::ShowResults
. MainPage::MakeWordList
y MainPage::FindCommonWords
realizan operaciones que requieren gran cantidad de recursos de computación. El método MainPage::ShowResults
muestra el resultado del cálculo en la interfaz de usuario.
// 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 el constructor MainPage
para crear una cadena de tareas de continuación que muestra en la interfaz de usuario las palabras comunes del libro La Ilíada de Homero. Las dos primeras tareas de continuación, que dividen el texto en palabras individuales y encuentran palabras comunes, pueden exigir mucho tiempo y, por consiguiente, se establecen explícitamente para que se ejecuten en segundo plano. La tarea final de continuación, que actualiza la interfaz de usuario, no especifica ningún contexto de continuación y, por consiguiente, sigue las reglas del subproceso contenedor.
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:
En este ejemplo se muestra cómo especificar contextos de ejecución y cómo crear una cadena de continuaciones. Recuerde que de forma predeterminada una tarea que se crea a partir de una operación asincrónica ejecuta sus continuaciones en el contenedor que llamó a task::then
. Por consiguiente, este ejemplo usa task_continuation_context::use_arbitrary
para especificar que las operaciones que no implican la interfaz de usuario se realizan en un subproceso en segundo plano.
En la ilustración siguiente se muestran los resultados de la aplicación CommonWords
.
En este ejemplo, es posible admitir la cancelación porque los objetos task
que admiten create_async
usan un token de cancelación implícito. Defina la función de trabajo para tomar un objeto cancellation_token
si las tareas deben responder a la cancelación de manera cooperativa. Para obtener más información sobre la cancelación en la PPL, consulte Cancellation in the PPL