Поток управления в асинхронных программах (C# и Visual Basic)
Можно легче создавать и поддерживать асинхронные программы с помощью ключевых слов Async и Await. Однако при непонимании работы программы результаты могут удивить. В этом разделе отслеживается поток управления в простой асинхронной программе для того, чтобы показать, когда управление передается от одного метода к другому, и какая информация передается каждый раз.
Примечание
Ключевые слова Async и Await были введены в Visual Studio 2012.
Как правило, методы, содержащие асинхронный код, помечаются модификатором Async (Visual Basic) или async (C#). В методе, помеченном модификатором async, можно использовать оператор Await (Visual Basic) или await (C#), чтобы определить, где метод приостанавливается для ожидания завершения вызванного асинхронного процесса. Для получения дополнительной информации см. Асинхронное программирование с использованием ключевых слов Async и Await (C# и Visual Basic).
В следующем примере используются асинхронные методы для загрузки содержимого конкретного веб-сайта в виде строки и отображения длины этой строки. Пример содержит следующие два метода.
startButton_Click, который вызывает AccessTheWebAsync и отображает результат.
AccessTheWebAsync, который загружает содержимое веб-сайта в виде строки и возвращает длину строки. AccessTheWebAsync использует асинхронный метод HttpClient, GetStringAsync(String) для загрузки содержимого.
Нумерованные выводимые строки играют роль стратегических точек в программе, помогая понять ход выполнения программы и те действия, которые происходят в каждой отмеченной точке. Выводимые строки отмечены от "ОДИН" до "ШЕСТЬ". Метки представляют собой порядок, в котором программа достигает эти строки кода.
В следующем коде показана структура программы.
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;
}
}
Каждое из расположений, обозначенных от "ONE" до "SIX", отображает сведения о текущем состоянии программы. Следующие выходные данные являются результатом.
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.
Настройка программы
Можно загрузить используемый в этом разделе код из MSDN, или же можно самостоятельно создать его самостоятельно.
Примечание
Для запуска примера необходимо, чтобы на компьютере была установлена Visual Studio 2012, Visual Studio 2013, Visual Studio Express 2012, Visual Studio Express 2013 для Windows, или .NET Framework 4.5 или 4.5.1.
Загрузка программы
Можно загрузить приложение из Пример асинхронности. Поток управления в асинхронных программах. В следующих шагах программа открывается и запускается.
Распакуйте загруженный файл, а затем запустите Visual Studio.
В строке меню выберите Файл, Открыть, Проект/Решение.
Перейдите к папке, содержащей распакованный образец кода, откройте файл решения (sln), а затем нажмите клавишу F5 для сборки и выполнения проекта.
Самостоятельное построение программы
Следующий проект Windows Presentation Foundation (WPF) содержит пример кода для данного раздела.
Чтобы запустить этот проект, выполните указанные ниже действия:
Запустите Visual Studio.
В меню Файл выберите Создать, Проект.
Откроется диалоговое окно Новый проект.
На панели Установленные шаблоны выберите Visual Basic или Visual C#, а затем выберите Приложение WPF из списка типов проектов.
Введите в качестве имени проекта AsyncTracer, а затем нажмите кнопку ОК.
В обозревателе решений появится новый проект.
Выберите в редакторе кода Visual Studio вкладку MainWindow.xaml.
Если вкладка не отображается, откройте контекстное меню для MainWindow.xaml в обозревателе решений, а затем выберите Просмотреть код.
В представлении XAML MainWindow.xaml замените код на следующий код.
<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>
Простое окно, содержащее текстовое поле и кнопку, отображается в представлении Конструктор MainWindow.xaml.
Добавьте ссылку на System.Net.Http.
В Обозревателе решений откройте контекстное меню элемента MainWindow.xaml.vb или MainWindow.xaml.cs и выберите команду Просмотреть код.
В файле MainWindow.xaml.vb или MainWindow.xaml.cs замените код на следующий.
' 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; } } }
Нажмите клавишу F5, чтобы запустить программу, затем нажмите кнопку Start.
Появятся следующие выходные данные.
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.
Трассировка программы
Шаги ОДИН и ДВА
Первые две выходные строки отслеживают путь, когда startButton_Click вызывает AccessTheWebAsync, а AccessTheWebAsync вызывает асинхронный HttpClient метод GetStringAsync(String). Следующее изображение показывает вызовы методов из методов.
Тип возвращаемого значения AccessTheWebAsync и client.GetStringAsync — Task. Для AccessTheWebAsync TResult — целое число. Для GetStringAsync TResult — строка. Дополнительные сведения о возвращаемых типах асинхронных методов см. в разделе Асинхронные типы возвращаемых значений (C# и Visual Basic).
Асинхронный метод, возвращающий задачу, возвращает экземпляр задачи, когда элемент управления возвращается к вызывающему объекту. Управление передается от асинхронного метода его вызывающему объекту, когда в вызванном методе обнаруживается оператор Await или await, или вызванный метод завершается. Отображаемые строки, которые помечены от "ТРИ" до "ШЕСТЬ", отслеживают эту часть процесса.
Шаг ТРИ
В AccessTheWebAsync, асинхронный метод GetStringAsync(String) вызывается, чтобы загрузить содержимое целевой веб-страницы. Управление передается от client.GetStringAsync к AccessTheWebAsync при возврате client.GetStringAsync.
Метод client.GetStringAsync возвращает задачу строки, которая назначена переменной getStringTask в AccessTheWebAsync. Следующая строка в примере программы показывает вызов client.GetStringAsync и присваивание.
Dim getStringTask As Task(Of String) = client.GetStringAsync("https://msdn.microsoft.com")
Task<string> getStringTask = client.GetStringAsync("https://msdn.microsoft.com");
Можно представить себе задачу как обещание client.GetStringAsync чтобы однажды создать фактическую строку. Тем временем, если AccessTheWebAsync имеет задания, которые не зависят от обещанной строки из client.GetStringAsync, он может продолжать выполнять работу, пока client.GetStringAsync ожидает. В этом примере следующие строки вывода, которые обозначены "THREE", представляют возможность сделать независимую работу.
THREE: Back in AccessTheWebAsync.
Task getStringTask is started.
About to await getStringTask & return a Task<int> to startButton_Click.
Следующий оператор приостанавливает ход выполнения AccessTheWebAsync при ожидании getStringTask.
Dim urlContents As String = Await getStringTask
string urlContents = await getStringTask;
Следующее изображение показывает поток управления из client.GetStringAsync к присваиванию getStringTask, и от создания getStringTask к применению оператора await.
Выражение await приостанавливает AccessTheWebAsync до тех пор, пока не выполнит возврат client.GetStringAsync. Тем временем, управление возвращается к вызывающему объекту метода AccessTheWebAsync, startButton_Click.
Примечание
Как правило, вызов асинхронного метода ожидается немедленно.Например, одно из следующих присваиваний может заменить предыдущий код, который создает, а затем ожидает getStringTask:
-
В Visual Basic: Dim urlContents As String = Await client.GetStringAsync("https://msdn.microsoft.com")
-
C#: string urlContents = await client.GetStringAsync("https://msdn.microsoft.com");
В этом разделе, оператор await применяется далее так, чтобы вместить выходные строки, которые помечают поток управления внутри программы.
Шаг ЧЕТЫРЕ
Объявленный тип возвращаемого значения AccessTheWebAsync — Task(Of Integer) в Visual Basic и Task<int> — в C#. Поэтому при приостановке AccessTheWebAsync, он возвращает задачу типа целого числа в startButton_Click. Необходимо понимать, что, возвращаемая задача не getStringTask. Возвращаемая задача — новая задача типа целого числа, представляющая то, что еще нужно сделать в приостановленном методе, AccessTheWebAsync. Задача — обещание AccessTheWebAsync создать целое число, когда задача будет завершена.
Следующий оператор присвоит эту задачу переменной getLengthTask.
Dim getLengthTask As Task(Of Integer) = AccessTheWebAsync()
Task<int> getLengthTask = AccessTheWebAsync();
Например, в AccessTheWebAsync startButton_Click может продолжать работу, которая не зависит от результатов асинхронной задачи (getLengthTask) до тех пор, пока задача не ожидается. Следующие выходные строки представляют эту работу.
FOUR: Back in startButton_Click.
Task getLengthTask is started.
About to await getLengthTask -- no caller to return to.
Ход выполнения: startButton_Click приостанавливается при ожидании getLengthTask. Следующий оператор присваивания приостанавливает startButton_Click до тех пор, пока AccessTheWebAsync не будет завершен.
Dim contentLength As Integer = Await getLengthTask
int contentLength = await getLengthTask;
На следующем рисунке стрелки указывают поток управления из выражения await в AccessTheWebAsync к присваиванию значения getLengthTask, которое продолжается нормальной обработкой в startButton_Click до тех пор, пока не ожидается getLengthTask.
Шаг ПЯТЬ
Когда client.GetStringAsync сигнализирует, что он завершен, обработка в AccessTheWebAsync освобождается от приостановки и может продолжаться после оператора await. Следующие выходные строки представляют продолжение обработки.
FIVE: Back in AccessTheWebAsync.
Task getStringTask is complete.
Processing the return statement.
Exiting from AccessTheWebAsync.
Операнд выражения return, urlContents.Length, сохраняется в задаче, которую возвращает AccessTheWebAsync. Выражение await извлекает это значение из getLengthTask в startButton_Click.
Следующее изображение показывает передачу управления после завершения client.GetStringAsync (и getStringTask).
AccessTheWebAsync выполняется до завершения, и управление возвращается к startButton_Click, который ожидает завершения.
Шаг ШЕСТЬ
Когда AccessTheWebAsync сигнализирует, что он завершен, обработка может продолжаться после выражения await в startButton_Async. Фактически, программе больше делать нечего.
Следующие выходные строки представляют продолжение обработки в startButton_Async:
SIX: Back in startButton_Click.
Task getLengthTask is finished.
Result from AccessTheWebAsync is stored in contentLength.
About to display contentLength and exit.
Выражение await извлекает из getLengthTask целое число, являющееся операндом в выражении return в AccessTheWebAsync. Следующие выражение присваивает это значение переменной contentLength.
Dim contentLength As Integer = Await getLengthTask
int contentLength = await getLengthTask;
Следующее изображение показывает возврат управления от AccessTheWebAsync к startButton_Click.
См. также
Задачи
Пошаговое руководство. Использование отладчика с асинхронными методами
Основные понятия
Асинхронное программирование с использованием ключевых слов Async и Await (C# и Visual Basic)
Асинхронные типы возвращаемых значений (C# и Visual Basic)
Другие ресурсы
Пример асинхронности. Поток управления в асинхронных программах (C# и Visual Basic)