Controlar el flujo en los programas asincrónicos (C#)Control flow in async programs (C#)

Puede escribir y mantener los programas asincrónicos más fácilmente usando las palabras clave async y await.You can write and maintain asynchronous programs more easily by using the async and await keywords. Aun así, los resultados pueden sorprenderle si no sabe cómo funciona el programa.However, the results might surprise you if you don't understand how your program operates. En este tema se hace un seguimiento del flujo de control a través de un programa asincrónico simple en el que se muestra cuándo se mueve el control de un método a otro y qué información se transfiere cada vez.This topic traces the flow of control through a simple async program to show you when control moves from one method to another and what information is transferred each time.

En general, los métodos que contienen código asincrónico se marcan con el modificador async (C#).In general, you mark methods that contain asynchronous code with the async (C#) modifier. En un método que está marcado con un modificador async, puede usar un operador await (C#) para especificar dónde se para el método para esperar a que concluya un proceso asincrónico al que se ha llamado.In a method that's marked with an async modifier, you can use an await (C#) operator to specify where the method pauses to wait for a called asynchronous process to complete. Para obtener más información, vea Programación asincrónica con async y await (C#).For more information, see Asynchronous Programming with async and await (C#).

En el ejemplo siguiente se usan métodos asincrónicos para descargar el contenido de un sitio web especificado como una cadena y mostrar la longitud de la cadena.The following example uses async methods to download the contents of a specified website as a string and to display the length of the string. El ejemplo contiene los dos métodos siguientes:The example contains the following two methods.

  • startButton_Click, que llama a AccessTheWebAsync y muestra el resultado.startButton_Click, which calls AccessTheWebAsync and displays the result.

  • AccessTheWebAsync, que descarga el contenido de un sitio web como una cadena y devuelve la longitud de esta.AccessTheWebAsync, which downloads the contents of a website as a string and returns the length of the string. AccessTheWebAsync usa un método HttpClient asincrónico, GetStringAsync(String), para descargar el contenido.AccessTheWebAsync uses an asynchronous HttpClient method, GetStringAsync(String), to download the contents.

Las líneas de visualización numeradas aparecen en puntos estratégicos de todo el programa para ayudarle a entender cómo se ejecuta el programa y explicar lo que ocurre en cada punto marcado.Numbered display lines appear at strategic points throughout the program to help you understand how the program runs and to explain what happens at each point that is marked. Las líneas de visualización tienen las etiquetas comprendidas entre "UNO" y "SEIS".The display lines are labeled "ONE" through "SIX." Las etiquetas representan el orden en el que el programa alcanza estas líneas de código.The labels represent the order in which the program reaches these lines of code.

En el código siguiente se muestra un esquema del programa.The following code shows an outline of the program.

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 +=
            $"\r\nLength of the downloaded string: {contentLength}.\r\n";
    }

    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 (del "UNO" al "SEIS") muestra información sobre el estado actual del programa.Each of the labeled locations, "ONE" through "SIX," displays information about the current state of the program. Se produce el siguiente resultado:The following output is produced:

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 programaSet up the program

Puede descargar el código que se usa en este tema desde MSDN o crearlo usted mismo.You can download the code that this topic uses from MSDN, or you can build it yourself.

Nota

Para ejecutar el ejemplo, debe tener instalado en el equipo Visual Studio 2012 o posterior y .NET Framework 4.5 o posterior.To run the example, you must have Visual Studio 2012 or newer and the .NET Framework 4.5 or newer installed on your computer.

Descargar el programaDownload the program

Puede descargar la aplicación para este tema en Ejemplo de Async: Flujo de control en programas asincrónicos.You can download the application for this topic from Async Sample: Control Flow in Async Programs. Con los siguientes pasos se abre y se ejecuta el programa.The following steps open and run the program.

  1. Descomprima el archivo descargado e inicie Visual Studio.Unzip the downloaded file, and then start Visual Studio.

  2. En la barra de menús, elija Archivo > Abrir > Proyecto/Solución.On the menu bar, choose File > Open > Project/Solution.

  3. Navegue hasta la carpeta que contiene el código de ejemplo descomprimido, abra el archivo de la solución (.sln) y presione la tecla F5 para compilar y ejecutar el proyecto.Navigate to the folder that holds the unzipped sample code, open the solution (.sln) file, and then choose the F5 key to build and run the project.

Crear el programa usted mismoCreate the program Yourself

El siguiente proyecto de Windows Presentation Foundation (WPF) contiene el ejemplo de código de este tema.The following Windows Presentation Foundation (WPF) project contains the code example for this topic.

Para ejecutar el proyecto, realice los pasos siguientes:To run the project, perform the following steps:

  1. Inicie Visual Studio.Start Visual Studio.

  2. En la barra de menús, elija Archivo > Nuevo > Proyecto.On the menu bar, choose File > New > Project.

    Aparece el cuadro de diálogo Nuevo proyecto .The New Project dialog box opens.

  3. Elija la categoría Instalado > Visual C# > Escritorio de Windows y luego elija Aplicación de WPF en la lista de plantillas de proyecto.Choose the Installed > Visual C# > Windows Desktop category, and then choose WPF App from the list of project templates.

  4. Escriba AsyncTracer como el nombre del proyecto y elija el botón Aceptar.Enter AsyncTracer as the name of the project, and then choose the OK button.

    El proyecto nuevo aparece en el Explorador de soluciones.The new project appears in Solution Explorer.

  5. En el Editor de código de Visual Studio, elija la pestaña MainWindow.xaml .In the Visual Studio Code Editor, choose the MainWindow.xaml tab.

    Si la pestaña no está visible, abra el menú contextual de MainWindow.xaml en el Explorador de soluciones y elija Ver código.If the tab isn’t visible, open the shortcut menu for MainWindow.xaml in Solution Explorer, and then choose View Code.

  6. En la vista XAML de MainWindow.xaml, reemplace el código por el código siguiente.In the XAML view of MainWindow.xaml, replace the code with the following code.

    <Window
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://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>
    

    En la vista Diseño de MainWindow.xaml aparece una ventana simple que contiene un cuadro de texto y un botón.A simple window that contains a text box and a button appears in the Design view of MainWindow.xaml.

  7. Agregue una referencia para System.Net.Http.Add a reference for System.Net.Http.

  8. En el Explorador de soluciones, abra el menú contextual de MainWindow.xaml.cs y después elija Ver código.In Solution Explorer, open the shortcut menu for MainWindow.xaml.cs, and then choose View Code.

  9. Reemplace el código del archivo MainWindow.xaml.cs por el código siguiente.In MainWindow.xaml.cs, replace the code with the following code.

    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 +=
                    $"\r\nLength of the downloaded string: {contentLength}.\r\n";
            }
    
            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. Presione la tecla F5 para ejecutar el programa y elija el botón Inicio.Choose the F5 key to run the program, and then choose the Start button.

    Aparece el siguiente resultado:The following output appears:

    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.
    

Hacer un seguimiento del programaTrace the program

Pasos UNO y DOSSteps ONE and TWO

Las dos primeras líneas siguen la ruta de acceso a medida que startButton_Click llama a AccessTheWebAsync y AccessTheWebAsync llama al método HttpClient asincrónico GetStringAsync(String).The first two display lines trace the path as startButton_Click calls AccessTheWebAsync, and AccessTheWebAsync calls the asynchronous HttpClient method GetStringAsync(String). En la siguiente imagen se describen las llamadas de método a método.The following image outlines the calls from method to method.

Pasos UNO y DOSSteps ONE and TWO

El tipo de valor devuelto de AccessTheWebAsync y client.GetStringAsync es Task<TResult>.The return type of both AccessTheWebAsync and client.GetStringAsync is Task<TResult>. Para AccessTheWebAsync, TResult es un entero.For AccessTheWebAsync, TResult is an integer. Para GetStringAsync, TResult es una cadena.For GetStringAsync, TResult is a string. Para obtener más información sobre los tipos de valor devuelto de los métodos asincrónicos, vea Tipos de valor devueltos asincrónicos (C#).For more information about async method return types, see Async Return Types (C#).

Un método asincrónico de devolución de tarea devuelve una instancia de la tarea cuando el control se desplaza al llamador.A task-returning async method returns a task instance when control shifts back to the caller. El control vuelve a su llamador procedente de un método asincrónico cuando se encuentra un operador await en el método llamado o cuando este finaliza.Control returns from an async method to its caller either when an await operator is encountered in the called method or when the called method ends. Las líneas de visualización que tienen las etiquetas comprendidas entre "TRES" y "SEIS" rastrean esta parte del proceso.The display lines that are labeled "THREE" through "SIX" trace this part of the process.

Paso TRESStep THREE

En AccessTheWebAsync, el método asincrónico GetStringAsync(String) se llama para descargar el contenido de la página web de destino.In AccessTheWebAsync, the asynchronous method GetStringAsync(String) is called to download the contents of the target webpage. El control vuelve a AccessTheWebAsync procedente de client.GetStringAsync cuando se devuelve client.GetStringAsync.Control returns from client.GetStringAsync to AccessTheWebAsync when client.GetStringAsync returns.

El método client.GetStringAsync devuelve una tarea de la cadena que se asigna a la variable getStringTask en AccessTheWebAsync.The client.GetStringAsync method returns a task of string that’s assigned to the getStringTask variable in AccessTheWebAsync. En la siguiente línea del programa de ejemplo se muestra la llamada a client.GetStringAsync y la asignación.The following line in the example program shows the call to client.GetStringAsync and the assignment.

Task<string> getStringTask = client.GetStringAsync("https://msdn.microsoft.com");

Puede considerar la tarea como una promesa de client.GetStringAsync de generar una cadena real.You can think of the task as a promise by client.GetStringAsync to produce an actual string eventually. Mientras tanto, si AccessTheWebAsync tiene trabajo que no depende de la cadena prometida de client.GetStringAsync, dicho trabajo puede continuar mientras client.GetStringAsync espera.In the meantime, if AccessTheWebAsync has work to do that doesn't depend on the promised string from client.GetStringAsync, that work can continue while client.GetStringAsync waits. En el ejemplo, las siguientes líneas de salida, que tienen la etiqueta "TRES", representan la oportunidad de realizar un trabajo independienteIn the example, the following lines of output, which are labeled "THREE," represent the opportunity to do independent work

THREE: Back in AccessTheWebAsync.
           Task getStringTask is started.
           About to await getStringTask & return a Task<int> to startButton_Click.

La siguiente instrucción suspende el progreso en AccessTheWebAsync cuando se espera a getStringTask.The following statement suspends progress in AccessTheWebAsync when getStringTask is awaited.

string urlContents = await getStringTask;

En la siguiente imagen se muestra el flujo de control procedente de client.GetStringAsync a la asignación de getStringTask y procedente de la creación de getStringTask a la aplicación de un operador await.The following image shows the flow of control from client.GetStringAsync to the assignment to getStringTask and from the creation of getStringTask to the application of an await operator.

Paso TRESStep THREE

La expresión await suspende AccessTheWebAsync hasta que se devuelva client.GetStringAsync.The await expression suspends AccessTheWebAsync until client.GetStringAsync returns. Mientras tanto, el control vuelve al llamador de AccessTheWebAsync, startButton_Click.In the meantime, control returns to the caller of AccessTheWebAsync, startButton_Click.

Nota

Normalmente se espera la llamada a un método asincrónico de forma inmediata.Typically, you await the call to an asynchronous method immediately. Por ejemplo, la siguiente asignación podría reemplazar el código anterior que crea y espera getStringTask: string urlContents = await client.GetStringAsync("https://msdn.microsoft.com");For example, the following assignment could replace the previous code that creates and then awaits getStringTask: string urlContents = await client.GetStringAsync("https://msdn.microsoft.com");

En este tema, el operador await se aplica más adelante para dar cabida a las líneas de salida que marcan el flujo de control a través del programa.In this topic, the await operator is applied later to accommodate the output lines that mark the flow of control through the program.

Paso CUATROStep FOUR

El tipo de valor devuelto declarado de AccessTheWebAsync es Task<int>.The declared return type of AccessTheWebAsync is Task<int>. Por lo tanto, cuando se suspende AccessTheWebAsync, devuelve una tarea de entero en startButton_Click.Therefore, when AccessTheWebAsync is suspended, it returns a task of integer to startButton_Click. Debe entender que la tarea devuelta no es getStringTask.You should understand that the returned task isn’t getStringTask. La tarea devuelta es una nueva tarea de entero que representa lo que falta por hacer en el método suspendido, AccessTheWebAsync.The returned task is a new task of integer that represents what remains to be done in the suspended method, AccessTheWebAsync. La tarea es una promesa de AccessTheWebAsync de generar un entero cuando finalice la tarea.The task is a promise from AccessTheWebAsync to produce an integer when the task is complete.

La siguiente instrucción asigna esta tarea a la variable getLengthTask.The following statement assigns this task to the getLengthTask variable.

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 espere la tarea.As in AccessTheWebAsync, startButton_Click can continue with work that doesn’t depend on the results of the asynchronous task (getLengthTask) until the task is awaited. Las siguientes líneas de salida representan ese trabajo.The following output lines represent that work.

FOUR:  Back in startButton_Click.
           Task getLengthTask is started.
           About to await getLengthTask -- no caller to return to.

El progreso de startButton_Click se suspende cuando se espera getLengthTask.Progress in startButton_Click is suspended when getLengthTask is awaited. La siguiente instrucción de asignación suspende startButton_Click hasta que concluya AccessTheWebAsync.The following assignment statement suspends startButton_Click until AccessTheWebAsync is complete.

int contentLength = await getLengthTask;

En la siguiente ilustración, las flechas muestran el flujo de control desde la expresión await en AccessTheWebAsync hasta la asignación de un valor a getLengthTask, seguido del procesamiento normal en startButton_Click hasta que se espera a getLengthTask.In the following illustration, the arrows show the flow of control from the await expression in AccessTheWebAsync to the assignment of a value to getLengthTask, followed by normal processing in startButton_Click until getLengthTask is awaited.

Paso CUATROStep FOUR

Paso CINCOStep FIVE

Cuando client.GetStringAsync indica que ha finalizado, el procesamiento de AccessTheWebAsync sale de la suspensión y puede continuar una vez superada la instrucción await.When client.GetStringAsync signals that it’s complete, processing in AccessTheWebAsync is released from suspension and can continue past the await statement. En las siguientes líneas de salida se representa la reanudación del procesamiento.The following lines of output represent the resumption of processing.

FIVE:  Back in AccessTheWebAsync.
           Task getStringTask is complete.
           Processing the return statement.
           Exiting from AccessTheWebAsync.

El operando de la instrucción de devolución, urlContents.Length, se almacena en la tarea que devuelve AccessTheWebAsync.The operand of the return statement, urlContents.Length, is stored in the task that AccessTheWebAsync returns. La expresión await recupera ese valor de getLengthTask en startButton_Click.The await expression retrieves that value from getLengthTask in startButton_Click.

En la siguiente imagen se muestra la transferencia de control una vez concluido client.GetStringAsync (y getStringTask).The following image shows the transfer of control after client.GetStringAsync (and getStringTask) are complete.

Paso CINCOStep FIVE

AccessTheWebAsync se ejecuta hasta el final y el control vuelve a startButton_Click, que espera la finalización.AccessTheWebAsync runs to completion, and control returns to startButton_Click, which is awaiting the completion.

Paso SEISStep SIX

Cuando AccessTheWebAsync indica que ha finalizado, el procesamiento puede continuar una vez superada la instrucción await en startButton_Async.When AccessTheWebAsync signals that it’s complete, processing can continue past the await statement in startButton_Async. De hecho, el programa no tiene nada más que hacer.In fact, the program has nothing more to do.

En las siguientes líneas de salida se representa la reanudación del procesamiento en startButton_Async:The following lines of output represent the resumption of processing in 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 await recupera de getLengthTask el valor entero que es el operando de la instrucción de devolución de AccessTheWebAsync.The await expression retrieves from getLengthTask the integer value that’s the operand of the return statement in AccessTheWebAsync. La siguiente instrucción asigna ese valor a la variable contentLength.The following statement assigns that value to the contentLength variable.

int contentLength = await getLengthTask;

En la siguiente imagen se muestra la devolución del control de AccessTheWebAsync a startButton_Click.The following image shows the return of control from AccessTheWebAsync to startButton_Click.

Paso SEISStep SIX

Vea tambiénSee also