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.
Descompacte o arquivo que você baixou e inicie o Visual Studio.
Na barra de menu, escolha Arquivo, Abrir, Projeto/solução.
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:
Inicie o Visual Studio.
Na barra de menu, escolha Arquivo, Novo, Projeto.
A Caixa de diálogo Novo Projeto é exibida.
No painel Modelos Instalados, selecione Visual Basic ou Visual C#, e então selecione Aplicativo WPF na lista de tipos de projetos.
Digite AsyncTracer como o nome do projeto e clique no botão OK.
O novo projeto aparece no Gerenciador de Soluções.
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.
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
" 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.
Adicione uma referência para System.Net.Http.
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.
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; } } }
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.
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.
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 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.
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.
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)