Поток управления в асинхронных программах (C# и Visual Basic)
Можно легче создавать и поддерживать асинхронные программы с помощью ключевых слов Async и Await.Однако результаты могут удивить, если вы не понимаете, как программа работает.В этом разделе отслеживается поток управления в простой асинхронной программе для того, чтобы показать, когда управление передается от одного метода к другому, и какая информация передается каждый раз.
Примечание |
---|
Ключевые слова Async и Await были введены в Visual Studio 2012.Дополнительные сведения о новых функциях в этой версии см. в разделе Новые возможности 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) для загрузки содержимого.
Нумерованные выводимые строки играют роль стратегических точек в программе, помогая понять ход выполнения программы и те действия, которые происходят в каждой отмеченной точке.Выводимые строки отмечены от "ONE" до "SIX". Метки представляют собой порядок, в котором программа достигает эти строки кода.
В следующем коде показана структура программы.
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 Express 2012 или .NET Framework 4,5, на компьютере. |
Загрузка программы
Можно загрузить приложение для этого раздела из Асинхронный пример: поток управления в асинхронных программах.В следующих шагах программа открывается и запускается.
Распакуйте загруженный файл, а затем запустите Visual Studio 2012.
В строке меню выберите Файл, Открыть, Проект/Решение.
Перейдите к папке, содержащей распакованный образец кода, откройте файл решения (sln), а затем нажмите клавишу F5 для построения и выполнения проекта.
Построение программы самостоятельно
Следующий проект Windows Presentation Foundation (WPF) содержит пример кода для данного раздела.
Чтобы запустить этот проект, выполните указанные ниже действия:
Запустите Visual Studio.
В строке меню выберите Файл, Создать, Проект.
Откроется диалоговое окно Новый проект.
На панели Установленные шаблоны выберите Visual Basic или Visual C#, а затем выберите Приложение WPF из списка типов проектов.
Введите в качестве имени проекта AsyncTracer, а затем нажмите кнопку ОК.
В обозревателе решений появится новый проект.
Выберите в редакторе кода Visual Studio вкладку MainWindow.xaml.
Если вкладка не отображается, откройте контекстное меню для MainWindow.xaml в Обозреватель решений, а затем выберите Перейти к коду.
В представлении 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.
Трассировка программы
Шаги ONE и TWO
Первые две выходные строки трассируют путь того, как startButton_Click вызывает AccessTheWebAsync, а AccessTheWebAsync вызывает асинхронный HttpClient метод GetStringAsync(String).Следующее изображение показывает вызовы методов из методов.
Тип возвращаемого значения обоих AccessTheWebAsync и client.GetStringAsync — Task<TResult>.Для AccessTheWebAsync TResult — целое число.Для GetStringAsync TResult — строка.Дополнительные сведения о возвращаемых типах асинхронных методов см. в разделе Асинхронные типы возвращаемых значений (C# и Visual Basic).
Асинхронный метод, возвращающий задание, возвращает экземпляр задания, когда поток управления возвращается к вызывающему объекту.Управление передается от асинхронного метода к вызывающему, когда в вызывающем методе обнаружен оператор Await или await, или вызванный метод завершается.Выходные строки, которые помечены от "THREE" до "SIX", трассируют эту часть процесса.
Шаг THREE
В 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:
В этом разделе, оператор await применяется далее так, чтобы вместить выходные строки, которые помечают поток управления внутри программы. |
Шаг FOUR
Объявленный тип возвращаемого значения 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.
Шаг FIVE
Когда 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, который ожидает завершения.
Шаг SIX
Когда 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)