Share via


Fluxo de controle em programas assíncronos (C# e Visual Basic)

Você pode escrever e manter programas assíncronos mais facilmente usando as palavras-chave de Async e de Await. No entanto, os resultados podem surpreendê-lo, se você não entende como seu programa opera. Este tópico rastreia o fluxo de controle com um programa simples de async para mostrar quando o controle se move de um método para outro e quais informações são transferidas cada vez.

Dica

As palavras-chave de Async e de Await foram introduzidas no Visual Studio 2012.

Em geral, você marca métodos que contenham código assíncrono com o modificador Async (Visual Basic) ou async (C#). Em um método que foi marcado com um modificador assíncrono, você poderá usar um operador Await (Visual Basic) ou await (C#) para especificar onde o método pausa para aguardar a conclusão de um processo assíncrono chamado. Para obter mais informações, consulte Programação assíncrona com Async e Await (C# e Visual Basic).

O exemplo a seguir usa métodos assíncronos para baixar o conteúdo de um site especificado como uma cadeia de caracteres e exibir o comprimento da cadeia de caracteres. O exemplo contém os dois seguintes métodos.

  • startButton_Click, que chama AccessTheWebAsync e exibe o resultado.

  • AccessTheWebAsync, que baixa o conteúdo de um site como uma cadeia de caracteres e retorna o comprimento da cadeia de caracteres. AccessTheWebAsync usa um método assíncrono HttpClient , GetStringAsync(String), para baixar o conteúdo.

As linhas de exibição numeradas aparecem em pontos estratégicos em todo o programa para ajudá-lo a entender como o programa é executado e a explicar o que acontece em cada ponto que é marcado. As linhas de exibição são rotuladas de “UMA” a “SEIS”. Os rótulos representam a ordem na qual o programa atinge essas linhas de código.

O código a seguir mostra uma estrutura do 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 um dos locais rotulados “, UM” com “SEIS,” exibe informações sobre o estado atual do programa. A saída a seguir é produzida.

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 o Programa

Você pode baixar o código que este tópico usa do MSDN, ou pode compilá-lo você mesmo.

Dica

Para executar o exemplo, você deve ter o Visual Studio 2012, Visual Studio 2013, Visual Studio Express 2012, Visual Studio Express 2013 para Windows, ou o .NET Framework 4.5 ou 4.5.1 instalado no seu computador.

Baixar o Programa

Você pode baixar o aplicativo para este tópico em Exemplo de Async: Fluxo de controle em programas de Async. As etapas a seguir abrem e executam o programa.

  1. Descompacte o arquivo que você baixou e inicie o Visual Studio.

  2. Na barra de menu, escolha Arquivo, Abrir, Projeto/solução.

  3. Navegue até a pasta que contém o código de exemplo descompactado, abra o arquivo de solução (.sln), e então selecione a tecla F5 para compilar e executar o projeto.

Compilar o Programa Sozinho

O seguinte projeto do Windows Presentation Foundation (WPF) contém o exemplo de código para este tópico.

Para executar o projeto, execute as seguintes etapas:

  1. Inicie o Visual Studio.

  2. Na barra de menu, escolha Arquivo, Novo, Projeto.

    A Caixa de diálogo Novo Projeto é exibida.

  3. No painel Modelos Instalados, selecione Visual Basic ou Visual C#, e então selecione Aplicativo WPF na lista de tipos de projetos.

  4. Digite AsyncTracer como o nome do projeto e clique no botão OK.

    O novo projeto aparece no Gerenciador de Soluções.

  5. No Editor de Códigos do Visual Studio, escolha a guia MainWindow.xaml.

    Se a guia não está visível, abra o menu de atalho de MainWindow.xaml no Gerenciador de Soluções e escolha Exibir código.

  6. No modo de exibição XAML de MainWindow.xaml, substitua o código pelo seguinte.

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

    Uma janela simples que contém uma caixa de texto e um botão aparece no modo de exibição Design de MainWindow.xaml.

  7. Adicione uma referência para System.Net.Http.

  8. Em Gerenciador de Soluções, abra o menu de atalho para MainWindow.xaml.vb ou MainWindow.xaml.cs e, em seguida, escolha Exibir Código.

  9. Em MainWindow.xaml.vb ou MainWindow.xaml.cs, substitua o código com o código a seguir.

    ' 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. Escolha a tecla F5 para executar o programa e escolha o botão Iniciar.

    As seguintes saídas devem aparecer.

    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 o Programa

Etapas UM e DOIS

As duas primeiras linhas de exibição mostram o caminho como startButton_Click chama AccessTheWebAsync chama AccessTheWebAsync o método assíncrono GetStringAsync(String)de HttpClient . A imagem a seguir descreve as chamadas de método para método.

Etapas UM e DOIS

O tipo de retorno de ambos AccessTheWebAsync e client.GetStringAsync é Task. Para AccessTheWebAsync, TResult é um inteiro. Para GetStringAsync, TResult é uma cadeia de caracteres. Para obter mais informações sobre tipos de retorno de métodos assíncronos, consulte Tipos de retorno assíncronos (C# e Visual Basic).

Um método assíncrono de retorno de tarefa retorna uma instância de tarefa quando o controle volta para o chamador. O controle retorna de um método assíncrono para o chamador qualquer quando um operador Await ou await está localizado no método chamado ou quando o método chamado termina. As linhas de exibição que são rotuladas de “TRÊS” a “SEIS” rastreiam esta parte do processo.

Etapa TRÊS

Em AccessTheWebAsync, o método assíncrono GetStringAsync(String) é chamado para baixar o conteúdo da Web page de destino. O controle retorna de client.GetStringAsync para AccessTheWebAsync quando o client.GetStringAsync retorna.

O método de client.GetStringAsync retorna uma tarefa de cadeia de caracteres que é atribuída à variável de getStringTask em AccessTheWebAsync. A seguinte linha no programa do exemplo mostra a chamada para client.GetStringAsync e a atribuição.

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

Você pode pensar na tarefa como uma promessa do client.GetStringAsync de gerar uma cadeia de caracteres real. Enquanto isso, se AccessTheWebAsync tiver trabalho a fazer que não dependa da cadeia de caracteres prometida de client.GetStringAsync, o trabalho poderá continuar enquanto client.GetStringAsync aguarda. No exemplo, as seguintes linhas de saída, rotuladas como "TRÊS", representam a oportunidade de fazer o trabalho de forma independente

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

A seguinte declaração suspende o progresso em AccessTheWebAsync quando getStringTask é esperado.

Dim urlContents As String = Await getStringTask
string urlContents = await getStringTask;

A imagem a seguir mostra o fluxo de controle de client.GetStringAsync para a atribuição para getStringTask e da criação de getStringTask para o aplicativo de um operador de espera.

Etapa TRÊS

A expressão de espera suspende AccessTheWebAsync até que client.GetStringAsync retorna. Enquanto isso, o controle retorna ao chamador de AccessTheWebAsync, startButton_Click.

Dica

Normalmente, você espera a chamada para um método assíncrono imediatamente.Por exemplo, uma das seguintes atribuições poderiam substituir o código anterior que cria e 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");

Neste tópico, o operador await é aplicado posteriormente para acomodar as linhas de saída que marcam o fluxo de controle no programa.

Etapa QUATRO

O tipo de retorno declarado de AccessTheWebAsync é declarado Task(Of Integer) no Visual Basic e em Task<int> C#. Portanto, quando AccessTheWebAsync é suspenso, retorna uma tarefa de inteiro para startButton_Click. Você deve compreender que a tarefa retornada não é getStringTask. A tarefa retornada é uma nova tarefa do inteiro que representa o que resta ser feito no método suspenso, AccessTheWebAsync. A tarefa é uma promessa do AccessTheWebAsync de gerar um inteiro quando a tarefa for concluída.

A instrução a seguir atribui o valor desta tarefa à variável de getLengthTask .

Dim getLengthTask As Task(Of Integer) = AccessTheWebAsync()
Task<int> getLengthTask = AccessTheWebAsync();

Como em AccessTheWebAsync, startButton_Click pode continuar com o trabalho que não depende dos resultados da tarefa assíncrona (getLengthTask) enquanto a tarefa é aguardada. As seguintes linhas de saída representam este trabalho.

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

O progresso em startButton_Click é suspenso quando getLengthTask é esperado. A seguinte instrução de atribuição é suspensa startButton_Click até que AccessTheWebAsync seja concluída.

Dim contentLength As Integer = Await getLengthTask
int contentLength = await getLengthTask;

Na ilustração a seguir, as setas mostram o fluxo de controle da expressão await em AccessTheWebAsync para a atribuição de um valor a getLengthTask, seguido pelo processamento normal em startButton_Click até getLengthTask ser esperado.

Etapa QUATRO

Etapa CINCO

Quando o client.GetStringAsync sinalizar sua conclusão, o processamento no AccessTheWebAsync é liberado da suspensão de pode continuar após a declaração de espera. As seguintes linhas de saída representam o reinício do processamento.

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

O operando da declaração de retorno, urlContents.Length, é armazenado na tarefa que o AccessTheWebAsync retorna. A expressão de espera recupera o valor de getLengthTask em startButton_Click.

A imagem a seguir mostra a transferência de controle após client.GetStringAsync (e getStringTask) serem concluídas.

Etapa CINCO

AccessTheWebAsync executa a conclusão, e o controle retorna a startButton_Click, que está aguardando a conclusão.

Etapa SEIS

Quando o AccessTheWebAsync sinaliza que está concluído, o processamento pode continuar após a declaração de espera no startButton_Async. Na verdade, o programa não tem nada mais a fazer.

As seguintes linhas de saída representam o reinício do processamento em startButton_Async:

SIX:   Back in startButton_Click.
           Task getLengthTask is finished.
           Result from AccessTheWebAsync is stored in contentLength.
           About to display contentLength and exit.

A expressão de espera recuperar de getLengthTask o valor inteiro que é o operando de declaração return em AccessTheWebAsync. A instrução a seguir atribui o valor à variável de contentLength .

Dim contentLength As Integer = Await getLengthTask
int contentLength = await getLengthTask;

A imagem a seguir mostra o retorno de controle de AccessTheWebAsync para startButton_Click.

Etapa SEIS

Consulte também

Tarefas

Instruções passo a passo: acessando a Web e usando Async e Await (C# e Visual Basic)

Passo a passo: Usando o depurador com métodos assíncronos

Conceitos

Programação assíncrona com Async e Await (C# e Visual Basic)

Tipos de retorno assíncronos (C# e Visual Basic)

Outros recursos

Exemplo de Async: Fluxo de controle em programas Async (C# e Visual Basic)