Crear operaciones asincrónicas en C++ para aplicaciones de la Tienda Windows
En este documento se describen algunos de los puntos clave para tener en cuenta cuando se usa el runtime de simultaneidad para generar operaciones asincrónicas en una aplicación de la Tienda Windows.
El uso de la programación asincrónica es un componente clave en el modelo de aplicaciones de la Tienda Windows 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. El documento Programación asincrónica en C++ proporciona información general sobre el modelo asincrónico que está disponible en Visual C++ para crear aplicaciones Tienda Windows. En ese documento se enseña cómo utilizar y crear cadenas de operaciones asincrónicas de Windows en tiempo de ejecución. En esta sección se describe cómo usar el runtime de simultaneidad para generar operaciones asincrónicas que otro componente de Windows en tiempo de ejecución puede consumir, y cómo controlar la ejecución del trabajo asincrónico. También puede leer Modelos de programación asincrónica y sugerencias en Hilo (aplicaciones de la Tienda Windows con C++ y XAML) para obtener información sobre cómo se usa el Runtime de simultaneidad para implementar operaciones asincrónicas en Hilo, una aplicación de Tienda Windows que usa C++ y XAML.
Nota
Puede usar el Parallel Patterns Library (PPL) y Biblioteca de agentes asincrónicos en un la aplicación Tienda Windows.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 el runtime de simultaneidad disponibles solo para una aplicación de la Tienda Windows, 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> o task<void>) se ejecuta de manera sincrónica en el contexto que llamó a create_async. Una función de trabajo que devuelve T o void 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 de la Tienda Windows, 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
Ejemplo: crear un componente de Windows en tiempo de ejecución de C++
Controlar el subproceso de ejecución
Ejemplo: controlar la ejecución en una aplicación de la Tienda Windows con C++ y XAML
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 Paralelismo de tareas (Runtime de simultaneidad).
Windows en tiempo de ejecución es una interfaz de programación que se puede usar para crear aplicaciones de la Tienda Windows que solo se ejecutan en un entorno de sistema operativo especial. Esas aplicaciones usan funciones, tipos de datos y dispositivos autorizados, y se distribuyen desde la Tienda Windows. Windows en tiempo de ejecución 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 en tiempo de ejecución a disposición de los lenguajes de programación, como por ejemplo Visual C++.
Mediante Windows en tiempo de ejecución, puede utilizar 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 en tiempo de ejecución para comunicar operaciones asincrónicas a otros componentes (que pueden estar escritos en lenguajes distintos de C++). Windows en tiempo de ejecución 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++, el runtime de simultaneidad proporciona la función concurrency::create_async. Esta función crea una acción u operación asincrónica de Windows en tiempo de ejecución 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 en tiempo de ejecución.
Nota
Utilice create_async únicamente si tiene que crear una funcionalidad a la que se puede tener acceso desde otro lenguaje u otro componente de Windows en tiempo de ejecución.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 Cancelación en la biblioteca PPL. Puede conectar la cancelación de tareas con los métodos Windows en tiempo de ejecución Cancel 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 Tienda Windows con C# y XAML que usa un componente personalizado de Windows en tiempo de ejecución de C++.
Advertencia
En una cadena de continuaciones de tareas, siempre limpie el estado y llame a concurrency::cancel_current_task cuando concurrency::is_task_cancellation_requested devuelve true.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 en tiempo de ejecución |
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 en tiempo de ejecución 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.
});
});
[Arriba]
Ejemplo: crear un componente de Windows en tiempo de ejecución de C++ y usarlo desde C#
Piense en una aplicación que utiliza XAML y C# para definir la interfaz de usuario, y un componente de Windows en tiempo de ejecución 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 en tiempo de ejecución, comience, en Visual Studio, creando una Solución en blanco y asignándole el nombre Primes. A continuación, agregue a la solución un proyecto Componente de Windows en tiempo de ejecución 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 en tiempo de ejecución 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 de la Tienda Windows, 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 el nombre Primes al proyecto. A continuación, 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="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Primes"
xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="https://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 los patrones asincrónicos que están disponibles para C# y Visual Basic, consulte Patrones asincrónicos en las aplicaciones de la tienda Windows con C# y Patrones asincrónicos en las aplicaciones de la tienda Windows con VB.
Los métodos getPrimesCancellation y cancelGetPrimes colaboran para permitir al usuario cancelar la operación. Cuando el usuario selecciona el botón Cancelar, el método cancelGetPrimes llama 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 en tiempo de ejecución para comunicar que la cancelación ha finalizado. Para obtener más información sobre el modelo de cancelación, consulte Cancelación en la biblioteca PPL.
Importante
Para permitir que el runtime de simultaneidad informe correctamente a Windows en tiempo de ejecución 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 en tiempo de ejecución 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 obtener ejemplos que usan create_async para crear tareas asincrónicas que pueden usarse en otros lenguajes, consulte Usar C++ en el ejemplo de Optimizador de recorridos de Mapas de Bing y Windows 8 Asynchronous Operations in C++ with PPL.
[Arriba]
Controlar el subproceso de ejecución
Windows en tiempo de ejecución 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 procedente de los subprocesos en segundo plano deben calcularse de nuevo hacia el ASTA para actualizar la interfaz de usuario. En aplicaciones de la Tienda Windows 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 auxiliares 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 de la Tienda Windows 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 de la Tienda Windows.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.
[Arriba]
Ejemplo: controlar la ejecución en una aplicación de Tienda Windows 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, comience, en Visual Studio, creando un proyecto de Tienda Windows Aplicación vacía (XAML) y asignándole 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 acerca de las funciones de aplicación y declaraciones, consulte Implementación y paquetes de aplicaciones (aplicaciones de Windows en tiempo de ejecución).
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 biblioteca PPL, consulte Cancelación en la biblioteca PPL
[Arriba]