Flujo de control en programas asincrónicos (C# y Visual Basic)
Se pueden crear y mantener programas asincrónicos más fácilmente mediante las palabras clave Async y Await.Sin embargo, los resultados podrían sorprenderle si no entiende cómo funciona el programa.Este tema sigue el flujo de control a través de un sencillo programa asincrónico para mostrar cuándo pasa el control de un método a otro y qué información se transfiere cada vez.
[!NOTA]
Las palabras clave de Async y de Await se introdujeron en Visual Studio 2012.Para obtener más información sobre las nuevas características de esa versión, vea Novedades de Visual Studio 2012.
Normalmente, se marcan métodos que contienen código asincrónico con el modificador Async (Visual Basic) o asincrónico (C#).En un método marcado con un modificador asincrónico, se puede utilizar un operador Await (Visual Basic) o await (C#) para especificar dónde se pausa el método para esperar a que se complete un proceso denominado asincrónico.Para obtener más información, vea Programación asincrónica con Async y Await (C# y Visual Basic).
El ejemplo siguiente utiliza métodos asincrónicos para descargar el contenido de un sitio web especificado como una cadena y mostrar la longitud de la cadena.El ejemplo contiene los siguientes dos métodos.
startButton_Click, que llama a AccessTheWebAsync y muestra el resultado.
AccessTheWebAsync, que descarga el contenido de un sitio web como una cadena y devuelve la longitud de la cadena.AccessTheWebAsync utiliza un método asincrónico HttpClient, GetStringAsync(String), para descargar el contenido.
Las líneas de pantalla numeradas aparecen en puntos estratégicos del programa para ayudarle a entender cómo se ejecuta el programa y qué ocurre en cada punto marcado.Las líneas presentadas se denominan desde “UNO” a “SEIS.” Las etiquetas representan el orden en que el programa llega a estas líneas de código.
El código siguiente muestra un esquema del programa.
Class MainWindow
Private Async Sub StartButton_Click(sender As Object, e As RoutedEventArgs) Handles StartButton.Click
' ONE
Dim getLengthTask As Task(Of Integer) = AccessTheWebAsync()
' FOUR
Dim contentLength As Integer = Await getLengthTask
' SIX
ResultsTextBox.Text &=
String.Format(vbCrLf & "Length of the downloaded string: {0}." & vbCrLf, contentLength)
End Sub
Async Function AccessTheWebAsync() As Task(Of Integer)
' TWO
Dim client As HttpClient = New HttpClient()
Dim getStringTask As Task(Of String) =
client.GetStringAsync("https://msdn.microsoft.com")
' THREE
Dim urlContents As String = Await getStringTask
' FIVE
Return urlContents.Length
End Function
End Class
public partial class MainWindow : Window
{
// . . .
private async void startButton_Click(object sender, RoutedEventArgs e)
{
// ONE
Task<int> getLengthTask = AccessTheWebAsync();
// FOUR
int contentLength = await getLengthTask;
// SIX
resultsTextBox.Text +=
String.Format("\r\nLength of the downloaded string: {0}.\r\n", contentLength);
}
async Task<int> AccessTheWebAsync()
{
// TWO
HttpClient client = new HttpClient();
Task<string> getStringTask =
client.GetStringAsync("https://msdn.microsoft.com");
// THREE
string urlContents = await getStringTask;
// FIVE
return urlContents.Length;
}
}
Cada una de las ubicaciones etiquetadas, desde “UNO” a “SEIS”, muestra información sobre el estado actual del programa.Se produce la siguiente salida
ONE: Entering startButton_Click.
Calling AccessTheWebAsync.
TWO: Entering AccessTheWebAsync.
Calling HttpClient.GetStringAsync.
THREE: Back in AccessTheWebAsync.
Task getStringTask is started.
About to await getStringTask & return a Task<int> to startButton_Click.
FOUR: Back in startButton_Click.
Task getLengthTask is started.
About to await getLengthTask -- no caller to return to.
FIVE: Back in AccessTheWebAsync.
Task getStringTask is complete.
Processing the return statement.
Exiting from AccessTheWebAsync.
SIX: Back in startButton_Click.
Task getLengthTask is finished.
Result from AccessTheWebAsync is stored in contentLength.
About to display contentLength and exit.
Length of the downloaded string: 33946.
Configurar el programa
Se puede descargar el código que este tema utiliza de MSDN, o se puede compilar personalmente.
[!NOTA]
Para ejecutar el ejemplo, debe tener Visual Studio 2012, Visual Studio Express 2012, o .NET Framework 4,5 instalado en el equipo.
Descargar el programa
Se puede descargar la aplicación para este tema de Ejemplo asincrónico: Flujo de control en programas asincrónicos.Los pasos siguientes abren y ejecutan el programa.
Descomprima el archivo descargado, e inicie Visual Studio 2012.
En la barra de menú, elija Archivo, Abrir, Proyecto/Solución.
Navegue por la carpeta que contiene el código de ejemplo descomprimido, abra el archivo de solución (.sln), y después presione la tecla F5 para compilar y ejecutar el proyecto.
Compile el programa Yourself
El proyecto siguiente de Windows Presentation Foundation contiene el ejemplo de código de este tema.
Para ejecutar el proyecto, realice los pasos siguientes:
Inicie Visual Studio.
En la barra de menú, elija Archivo, Nuevo, Proyecto.
Aparece el cuadro de diálogo Nuevo proyecto.
En el panel Plantillas instaladas, elija Visual Basic o Visual C# y, a continuación, elija Aplicación WPF de la lista de tipos de proyecto.
Escriba AsyncTracer como nombre del proyecto y elija el botón Aceptar.
El nuevo proyecto aparecerá en el Explorador de soluciones.
En el editor de código de Visual Studio, elija la pestaña MainWindow.xaml .
Si la pestaña no está visible, abra el acceso directo de MainWindow.xaml en el Explorador de solucionesy, a continuación, elija Ver código.
En la vista XAML MainWindow.xaml, reemplace el código por el código siguiente.
<Window xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="https://schemas.microsoft.com/expression/blend/2008" xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="MainWindow" Title="Control Flow Trace" Height="350" Width="525"> <Grid> <Button x:Name="StartButton" Content="Start" HorizontalAlignment="Left" Margin="221,10,0,0" VerticalAlignment="Top" Width="75"/> <TextBox x:Name="ResultsTextBox" HorizontalAlignment="Left" TextWrapping="Wrap" VerticalAlignment="Bottom" Width="510" Height="265" FontFamily="Lucida Console" FontSize="10" VerticalScrollBarVisibility="Visible" d:LayoutOverrides="HorizontalMargin"/> </Grid> </Window>
<Window xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="https://schemas.microsoft.com/expression/blend/2008" xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="AsyncTracer.MainWindow" Title="Control Flow Trace" Height="350" Width="592"> <Grid> <Button x:Name="startButton" Content="Start
" HorizontalAlignment="Left" Margin="250,10,0,0" VerticalAlignment="Top" Width="75" Height="24" Click="startButton_Click" d:LayoutOverrides="GridBox"/> <TextBox x:Name="resultsTextBox" HorizontalAlignment="Left" TextWrapping="Wrap" VerticalAlignment="Bottom" Width="576" Height="265" FontFamily="Lucida Console" FontSize="10" VerticalScrollBarVisibility="Visible" Grid.ColumnSpan="3"/> </Grid> </Window>
Una ventana simple que contiene un cuadro de texto y un botón aparece en la vista de Diseño de MainWindow.xaml.
Agregue una referencia para System.Net.Http.
En el Explorador de soluciones, abra el acceso directo de MainWindow.xaml.vb o MainWindow.xaml.cs y, a continuación, elija Código de la vista.
Reemplace el código de MainWindow.xaml.vb o MainWindow.xaml.cs por el código siguiente.
' Add an Imports statement and a reference for System.Net.Http. Imports System.Net.Http Class MainWindow Private Async Sub StartButton_Click(sender As Object, e As RoutedEventArgs) Handles StartButton.Click ' The display lines in the example lead you through the control shifts. ResultsTextBox.Text &= "ONE: Entering StartButton_Click." & vbCrLf & " Calling AccessTheWebAsync." & vbCrLf Dim getLengthTask As Task(Of Integer) = AccessTheWebAsync() ResultsTextBox.Text &= vbCrLf & "FOUR: Back in StartButton_Click." & vbCrLf & " Task getLengthTask is started." & vbCrLf & " About to await getLengthTask -- no caller to return to." & vbCrLf Dim contentLength As Integer = Await getLengthTask ResultsTextBox.Text &= vbCrLf & "SIX: Back in StartButton_Click." & vbCrLf & " Task getLengthTask is finished." & vbCrLf & " Result from AccessTheWebAsync is stored in contentLength." & vbCrLf & " About to display contentLength and exit." & vbCrLf ResultsTextBox.Text &= String.Format(vbCrLf & "Length of the downloaded string: {0}." & vbCrLf, contentLength) End Sub Async Function AccessTheWebAsync() As Task(Of Integer) ResultsTextBox.Text &= vbCrLf & "TWO: Entering AccessTheWebAsync." ' Declare an HttpClient object. Dim client As HttpClient = New HttpClient() ResultsTextBox.Text &= vbCrLf & " Calling HttpClient.GetStringAsync." & vbCrLf ' GetStringAsync returns a Task(Of String). Dim getStringTask As Task(Of String) = client.GetStringAsync("https://msdn.microsoft.com") ResultsTextBox.Text &= vbCrLf & "THREE: Back in AccessTheWebAsync." & vbCrLf & " Task getStringTask is started." ' AccessTheWebAsync can continue to work until getStringTask is awaited. ResultsTextBox.Text &= vbCrLf & " About to await getStringTask & return a Task(Of Integer) to StartButton_Click." & vbCrLf ' Retrieve the website contents when task is complete. Dim urlContents As String = Await getStringTask ResultsTextBox.Text &= vbCrLf & "FIVE: Back in AccessTheWebAsync." & vbCrLf & " Task getStringTask is complete." & vbCrLf & " Processing the return statement." & vbCrLf & " Exiting from AccessTheWebAsync." & vbCrLf Return urlContents.Length End Function End Class
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; // Add a using directive and a reference for System.Net.Http; using System.Net.Http; namespace AsyncTracer { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private async void startButton_Click(object sender, RoutedEventArgs e) { // The display lines in the example lead you through the control shifts. resultsTextBox.Text += "ONE: Entering startButton_Click.\r\n" + " Calling AccessTheWebAsync.\r\n"; Task<int> getLengthTask = AccessTheWebAsync(); resultsTextBox.Text += "\r\nFOUR: Back in startButton_Click.\r\n" + " Task getLengthTask is started.\r\n" + " About to await getLengthTask -- no caller to return to.\r\n"; int contentLength = await getLengthTask; resultsTextBox.Text += "\r\nSIX: Back in startButton_Click.\r\n" + " Task getLengthTask is finished.\r\n" + " Result from AccessTheWebAsync is stored in contentLength.\r\n" + " About to display contentLength and exit.\r\n"; resultsTextBox.Text += String.Format("\r\nLength of the downloaded string: {0}.\r\n", contentLength); } async Task<int> AccessTheWebAsync() { resultsTextBox.Text += "\r\nTWO: Entering AccessTheWebAsync."; // Declare an HttpClient object. HttpClient client = new HttpClient(); resultsTextBox.Text += "\r\n Calling HttpClient.GetStringAsync.\r\n"; // GetStringAsync returns a Task<string>. Task<string> getStringTask = client.GetStringAsync("https://msdn.microsoft.com"); resultsTextBox.Text += "\r\nTHREE: Back in AccessTheWebAsync.\r\n" + " Task getStringTask is started."; // AccessTheWebAsync can continue to work until getStringTask is awaited. resultsTextBox.Text += "\r\n About to await getStringTask and return a Task<int> to startButton_Click.\r\n"; // Retrieve the website contents when task is complete. string urlContents = await getStringTask; resultsTextBox.Text += "\r\nFIVE: Back in AccessTheWebAsync." + "\r\n Task getStringTask is complete." + "\r\n Processing the return statement." + "\r\n Exiting from AccessTheWebAsync.\r\n"; return urlContents.Length; } } }
Presione la tecla F5 para ejecutar el programa y, a continuación, elija el botón Iniciar.
Debe aparecer el siguiente resultado.
ONE: Entering startButton_Click. Calling AccessTheWebAsync. TWO: Entering AccessTheWebAsync. Calling HttpClient.GetStringAsync. THREE: Back in AccessTheWebAsync. Task getStringTask is started. About to await getStringTask & return a Task<int> to startButton_Click. FOUR: Back in startButton_Click. Task getLengthTask is started. About to await getLengthTask -- no caller to return to. FIVE: Back in AccessTheWebAsync. Task getStringTask is complete. Processing the return statement. Exiting from AccessTheWebAsync. SIX: Back in startButton_Click. Task getLengthTask is finished. Result from AccessTheWebAsync is stored in contentLength. About to display contentLength and exit. Length of the downloaded string: 33946.
Rastrear el programa
Pasos UNO y DOS
Las dos primeras líneas siguen la ruta de acceso a medida que startButton_Click llama a AccessTheWebAsyncy AccessTheWebAsync llama al método HttpClient asincrónico GetStringAsync(String).La imagen siguiente muestra las llamadas método a método.
El tipo de valor devuelto de AccessTheWebAsync y client.GetStringAsync es Task<TResult>.Para AccessTheWebAsync, TResult es un entero.Para GetStringAsync, TResult es una cadena.Para obtener más información sobre los tipos de valor devuelto del método asincrónico, consulte Tipos de valor devuelto de Async (C y Visual Basic).
Un método asincrónico de devolución de tarea, devuelve una instancia de tarea cuando el control vuelve al llamadorEl control se devuelve desde un método asincrónico al llamador cuando se encuentra un operador Await o await en el método o cuando finaliza el método llamado.Las líneas de muestra etiquetadas desde “TRES” a “SEIS” hacen el seguimiento de esta parte del proceso.
Paso TRES
En AccessTheWebAsync, el método asincrónico GetStringAsync(String) se denomina para descargar el contenido de la página Web de destino.El control vuelve de client.GetStringAsync a AccessTheWebAsync cuando client.GetStringAsync retorna.
El método client.GetStringAsync devuelve una tarea de cadena que se asigna a la variable getStringTask en AccessTheWebAsync.La siguiente línea del programa de ejemplo muestra la llamada a client.GetStringAsync y la asignación.
Dim getStringTask As Task(Of String) = client.GetStringAsync("https://msdn.microsoft.com")
Task<string> getStringTask = client.GetStringAsync("https://msdn.microsoft.com");
Se puede considerar que la tarea es una promesa de client.GetStringAsync para generar una cadena real finalmente.Mientras tanto, si AccessTheWebAsync tiene trabajo que hacer que no depende de la cadena prometida de client.GetStringAsync, ese trabajo puede continuar mientras client.GetStringAsync espera.En el ejemplo, las siguientes líneas del resultado, que se denominan “TRES,” representan la oportunidad de realizar el trabajo independiente
THREE: Back in AccessTheWebAsync.
Task getStringTask is started.
About to await getStringTask & return a Task<int> to startButton_Click.
El siguiente fragmento suspende el progreso en AccessTheWebAsync cuando se espera getStringTask .
Dim urlContents As String = Await getStringTask
string urlContents = await getStringTask;
La imagen siguiente muestra el flujo de control de client.GetStringAsync a la asignación a getStringTask y la creación de getStringTask a la aplicación de un operador de espera.
La expresión de espera suspende AccessTheWebAsync hasta que client.GetStringAsync retorne.Mientras tanto, el control vuelve al llamador de AccessTheWebAsync, startButton_Click.
[!NOTA]
Normalmente, se espera la llamada a un método asincrónico inmediatamente.Por ejemplo, una de las siguientes asignaciones podría reemplazar el código anterior que crea y luego espera getStringTask:
Visual Basic: Dim urlContents As String = Await client.GetStringAsync("https://msdn.microsoft.com")
C#: string urlContents = await client.GetStringAsync("https://msdn.microsoft.com");
En este tema, el operador de espera se aplica más tarde para alojar las líneas de salida que marcan el flujo de control con el programa.
Paso CUATRO
El tipo de valor devuelto declarado de AccessTheWebAsync es Task(Of Integer) en Visual Basic y Task<int> en C#.Por consiguiente, cuando se suspende AccessTheWebAsync, devuelve una tarea de entero a startButton_Click.Se debería entender que la tarea devuelta no es getStringTask.La tarea devuelta es una nueva tarea de entero que representa lo que queda por hacer en el método suspendido, AccessTheWebAsync.La tarea es una promesa de AccessTheWebAsync de generar un entero cuando finalice la tarea.
El siguiente fragmento asigna esta tarea a la variable getLengthTask.
Dim getLengthTask As Task(Of Integer) = AccessTheWebAsync()
Task<int> getLengthTask = AccessTheWebAsync();
Como en AccessTheWebAsync, startButton_Click se puede continuar con el trabajo que no depende de los resultados de la tarea asincrónica (getLengthTask) hasta que se espera la tarea.Las líneas de resultado siguientes representan ese trabajo.
FOUR: Back in startButton_Click.
Task getLengthTask is started.
About to await getLengthTask -- no caller to return to.
El progreso en startButton_Click se suspende cuando se espera getLengthTask.La instrucción de asignación siguiente suspende startButton_Click hasta que se complete AccessTheWebAsync.
Dim contentLength As Integer = Await getLengthTask
int contentLength = await getLengthTask;
En la siguiente ilustración, las flechas muestran el flujo de control de expresiones de espera en AccessTheWebAsync a la asignación de un valor a getLengthTask, seguida del procesamiento normal en startButton_Click hasta que se espera getLengthTask.
Paso CINCO
Cuando client.GetStringAsync indica que está completado, el procesamiento en AccessTheWebAsync se libera de la suspensión y puede continuar una vez pasado el fragmento de espera.Las siguientes líneas de salida representan la reanudación del procesamiento.
FIVE: Back in AccessTheWebAsync.
Task getStringTask is complete.
Processing the return statement.
Exiting from AccessTheWebAsync.
El operando del extracto de retorno, urlContents.Length, se almacena en la tarea que AccessTheWebAsync devuelve.La expresión de espera recupera el valor de getLengthTask en startButton_Click.
En la imagen siguiente se muestra la transferencia del control después de que client.GetStringAsync (y de que getStringTask) se ha completado.
AccessTheWebAsync se ejecuta por completo y el control retorna a startButton_Click, que está esperando la finalización.
Paso SEIS
Cuando AccessTheWebAsync señala que está completado, el procesamiento puede continuar, una vez pasado el fragmento de espera en startButton_Async.De hecho, el programa no tiene nada más que hacer.
Las siguientes líneas de salida representan la reanudación del procesamiento en startButton_Async:
SIX: Back in startButton_Click.
Task getLengthTask is finished.
Result from AccessTheWebAsync is stored in contentLength.
About to display contentLength and exit.
La expresión de espera recupera de getLengthTask el valor entero que es el operando del extracto de retorno en AccessTheWebAsync.El siguiente extracto asigna ese valor a la variable contentLength.
Dim contentLength As Integer = Await getLengthTask
int contentLength = await getLengthTask;
La siguiente imagen muestra la devolución del control desde AccessTheWebAsync a startButton_Click.
Vea también
Tareas
Walkthrough: Acceso a web usando Async y Await (C# y Visual Basic)
Tutorial: Usar el depurador con métodos asincrónicos
Conceptos
Programación asincrónica con Async y Await (C# y Visual Basic)
Tipos de valor devuelto de Async (C y Visual Basic)
Otros recursos
Ejemplo Asincrónico: Control del flujo en programas asincrónicos (C# y Visual Basic)