Flujo de control en programas asincrónicos (C# y Visual Basic)

Puede crear y mantener programas asincrónicos más fácilmente usando 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 Async y Await se incluyeron en 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 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.

Configurar el programa

Puede descargar el código que este tema utiliza de MSDN, o puede compilar personalmente.

Nota

Para ejecutar el ejemplo, debe tener Visual Studio 2012, Visual Studio 2013, Visual Studio Express 2012, Visual Studio Express 2013 para Windows, o .NET Framework 4.5 o 4.5.1 instalados en el equipo.

Descargar el programa

Puede descargar la aplicación para este tema desde Ejemplo Async: Flujo de control en programas Async (C# y Visual Basic). Los pasos siguientes abren y ejecutan el programa.

  1. Descomprima el archivo descargado, e inicie Visual Studio.

  2. En la barra de menú, elija Archivo, Abrir, Proyecto/Solución.

  3. Desplácese hasta la carpeta que contiene el código de ejemplo descomprimido, abra el archivo de solución (.sln), y después elija la tecla F5 para compilar y ejecutar el proyecto.

Compilar el programa usted mismo

El proyecto siguiente de Windows Presentation Foundation (WPF) contiene el ejemplo de código de este tema.

Para ejecutar el proyecto, realice los pasos siguientes:

  1. Inicie Visual Studio.

  2. En la barra de menús, elija Archivo, Nuevo, Proyecto.

    Aparece el cuadro de diálogo Nuevo proyecto.

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

  4. Escriba AsyncTracer como nombre del proyecto y elija el botón Aceptar.

    El nuevo proyecto aparecerá en el Explorador de soluciones.

  5. 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 soluciones y, a continuación, elija Ver código.

  6. 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&#xa;" 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 Diseño de MainWindow.xaml.

  7. Agregue una referencia para System.Net.Http.

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

  9. 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;
            }
        }
    }
    
  10. Elija la tecla F5 para ejecutar el programa y, a continuación, 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.

Pasos UNO y DOS

El tipo de valor devuelto de AccessTheWebAsync y client.GetStringAsync es Task. 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 llamador. El 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 llama para descargar el contenido de la página web de destino. El control vuelve de client.GetStringAsync a AccessTheWebAsync cuando client.GetStringAsync se devuelve.

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");

Piense en la tarea como 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 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.

Paso TRES

La expresión de espera suspende AccessTheWebAsync hasta que client.GetStringAsync se devuelva. 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 esperar a 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 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 CUATRO

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 getStringTask) se hayan completado.

Paso CINCO

AccessTheWebAsync se ejecuta por completo y el control vuelve 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.

Paso SEIS

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 Async: Flujo de control en programas Async (C# y Visual Basic)