Пошаговое руководство. Использование отладчика с асинхронными методами

С помощью функции Async можно вызывать асинхронные методы, не прибегая к использованию обратных вызовов или разделению кода между несколькими методами или лямбда-выражениями. Чтобы сделать синхронный код асинхронным, следует вызвать асинхронный метод вместо синхронного и добавить в код несколько ключевых слов. Дополнительные сведения см. в разделе Асинхронное программирование с использованием ключевых слов Async и Await (C# и Visual Basic).

В отладчике Visual Studio можно использовать команды Шаг с заходом, Шаг с обходом и Шаг с выходом с функцией Async. Также можно продолжать использовать точки останова, в частности для отслеживания потока управления в коде, содержащем оператор await. В этом пошаговом руководстве будут выполнены следующие задачи, которые можно выполнять в любом порядке.

  • Наблюдение за потоком управления в операторе await с помощью точек останова.

  • Изучение поведения команд Шаг с заходом и Шаг с обходом в операторах, содержащих оператор await.

  • Изучение поведения команды Шаг с выходом при ее использовании внутри асинхронного метода.

Отслеживание потока управления с помощью точек останова

Если в объявлении метода указать модификатор Async (Visual Basic) или async (C#), то в этом методе можно использовать оператор Await (Visual Basic) или await (C#). Выражение await связывает оператор await с задачей. При вызове выражения await для этой задачи текущий метод сразу же перестает выполняться и возвращает другую задачу. После завершения выполнения задачи, связанной с оператором await, возобновляется выполнение прерванного метода (вызвавшего выражение await). Дополнительные сведения см. в разделе Поток управления в асинхронных программах (C# и Visual Basic).

Примечание

Асинхронный метод возвращает управление вызвавшему его объекту, когда он встречает первый ожидаемый объект, выполнение которого еще не завершено, или когда он доходит до конца синхронного метода — в зависимости от того, что происходит раньше.

Примечание

В консольных приложениях в данных примерах используется метод Wait для предотвращения завершения работы приложения в методе Main.Не следует использовать метод Wait вне консольных приложений, так как это может привести к возникновению взаимоблокировки.

Ниже приведена процедура использования точек останова для изучения работы приложения при достижении им оператора await. Для контроля потока выполнения также можно добавить в программу операторы Debug.WriteLine.

  1. Создайте консольное приложение и вставьте в него следующий код:

    ' Breakpoints to show control flow. 
    Imports System.Threading.Tasks
    
    Module Module1
        Sub Main()
            Dim theTask = ProcessAsync()
            Dim x = 0 ' set breakpoint
            theTask.Wait()
        End Sub
    
        Async Function ProcessAsync() As Task
            Dim result = Await DoSomethingAsync()  ' set breakpoint
    
            Dim y = 0 ' set breakpoint
        End Function
    
        Async Function DoSomethingAsync() As Task(Of Integer)
            Await Task.Delay(1000)
            Return 5
        End Function 
    End Module
    
    // Breakpoints to show control flow. 
    using System.Threading.Tasks;
    
    class Program
    {
        static void Main(string[] args)
        {
            Task theTask = ProcessAsync();
            int x = 0;  // set breakpoint
            theTask.Wait();
        }
    
        static async Task ProcessAsync()
        {
            var result = await DoSomethingAsync();  // set breakpoint 
    
            int y = 0;  // set breakpoint
        }
    
        static async Task<int> DoSomethingAsync()
        {
            await Task.Delay(1000);
            return 5;
        }
    }
    
  2. Установите отладочные точки останова в трех строках, оканчивающихся комментарием "установить точку останова" ("set breakpoint").

  3. Нажмите клавишу F5 или в строке меню выберите Отладка, Начать отладку, чтобы запустить приложение.

    Выполнение приложения доходит до метода ProcessAsync и приостанавливается внутри этого метода на строке с оператором await.

  4. Снова нажмите клавишу F5.

    Так как выполнение приложения было остановлено на строке, содержащей выражение await, приложение немедленно выходит из асинхронного метода, при этом метод возвращает задачу. Следовательно, приложение выходит из метода ProcessAsync, после чего выполнение приостанавливается в точке останова внутри вызывающего метода (Main).

  5. Снова нажмите клавишу F5.

    После завершения выполнения метода DoSomethingAsync возобновляется выполнение кода, следующего за оператором await в вызывающем методе. Поэтому выполнение приложения приостанавливается в точке останова внутри метода ProcessAsync.

    Ранее, инициировав ожидание результата выполнения метода DoSomethingAsync, метод ProcessAsync передал управление вызвавшему его методу и вернул задачу. Теперь метод DoSomethingAsync завершил свою работу, а возвращенный им результат выполнения передан оператору await метода DoSomethingAsync. В объявлении метода DoSomethingAsync указано, что он возвращает Task (Of Integer) (в Visual Basic) или Task<int> (в C#), поэтому его оператор return возвращает целое число. Дополнительные сведения см. в разделе Асинхронные типы возвращаемых значений (C# и Visual Basic).

Получение задачи и ожидание ее выполнения

Оператор Dim result = Await DoSomethingAsync() (Visual Basic) или var result = await DoSomethingAsync(); (C#) в методе ProcessAsync представляет собой сокращенную форму записи двух следующих операторов:

Dim theTask = DoSomethingAsync()
Dim result = Await theTask
var theTask = DoSomethingAsync();
var result = await theTask;

Первая строка кода вызывает асинхронный метод и возвращает задачу. Следующая строка кода связывает эту задачу с оператором await. Оператор await производит выход из текущего метода (ProcessAsync) и возвращает другую задачу. После того как завершается выполнение задачи, связанной с оператором await, выполнение прерванного метода (ProcessAsync) возобновляется с кода, расположенного после оператора await.

Непосредственно в тот момент, когда оператор await возвращает другую задачу, эта задача представляет возвращаемый аргумент асинхронного метода, содержащего оператор await (то есть метода ProcessAsync). Задача, возвращаемая оператором await, включает выполнение кода, расположенного после оператора await в этом же методе, поэтому она отличается от задачи, связанной с оператором await.

Шаг с заходом и шаг с обходом

Команда Шаг с заходом заходит внутрь метода, тогда как команда Шаг с обходом выполняет вызов метода и останавливает выполнение на следующей строке вызывающего метода. Дополнительные сведения см. в Выполнение шагов с заходом в код, с обходом кода или выходом из него.

В следующей процедуре показано, что происходит при выборе команды Шаг с заходом или Шаг с обходом на строке с оператором await.

  1. Замените код в консольном приложении следующим кодом:

    ' Step Into and Step Over Example 
    Imports System.Threading.Tasks
    
    Module Module1
        Sub Main()
            ProcessAsync.Wait()
        End Sub
    
        Async Function ProcessAsync() As Task
            Dim result = Await DoSomethingAsync()  ' Step Into or Step Over from here
    
            Dim y = 0
        End Function
    
        Async Function DoSomethingAsync() As Task(Of Integer)
            Await Task.Delay(1000)
            Return 5
        End Function 
    End Module
    
    // Step Into and Step Over Example. 
    using System.Threading.Tasks;
    
    class Program
    {
        static void Main(string[] args)
        {
            ProcessAsync().Wait();
        }
    
        static async Task ProcessAsync()
        {
            var result = await DoSomethingAsync();  // Step Into or Step Over from here 
    
            int y = 0;
        }
    
        static async Task<int> DoSomethingAsync()
        {
            await Task.Delay(1000);
            return 5;
        }
    }
    
  2. Нажмите клавишу F11 или в строке меню выберите Отладка, Шаг с заходом, чтобы начать демонстрацию работы команды Step Into в отношении оператора, содержащего выражение await.

    Приложение начинает выполняться и останавливается на первой строке, а именно на строке Sub Main() (в Visual Basic) или на строке с открывающей фигурной скобкой метода Main (в C#).

  3. Нажмите клавишу F11 еще три раза.

    Теперь приложение должно находиться на операторе await внутри метода ProcessAsync.

  4. Нажмите клавишу F11.

    Приложение заходит в метод DoSomethingAsync и останавливается на первой строке. Команда "Шаг с заходом" работает таким образом несмотря на то, что при выполнении оператора await приложение немедленно возвращается в вызывающий метод (Main).

  5. Многократно нажимайте клавишу F11 до тех пор, пока приложение не вернется к оператору await метода ProcessAsync.

  6. Нажмите клавишу F11.

    Выполнение приложения приостанавливается на строке, следующей за оператором await.

  7. В строке меню выберите Отладка, Остановить отладку, чтобы остановить выполнение приложения.

    В следующей части процедуры демонстрируется работа команды Шаг с обходом в отношении оператора await.

  8. Нажмите четыре раза клавишу F11 или четыре раза выберите Отладка, Шаг с заходом в строке меню.

    Теперь приложение должно находиться на операторе await внутри метода ProcessAsync.

  9. Нажмите клавишу F10 или в строке меню выберите Отладка, Шаг с обходом.

    Выполнение приложения приостанавливается на строке, которая следует за оператором await. Команда "Шаг с обходом" работает таким образом несмотря на то, что при выполнении оператора await приложение немедленно возвращается в вызывающий метод (Main). При этом, как и ожидалось, команда Step Over обходит пошаговое выполнение метода DoSomethingAsync.

  10. В строке меню выберите Отладка, Остановить отладку, чтобы остановить выполнение приложения.

    Как будет показано в следующих шагах, поведение команд Шаг с заходом и Шаг с обходом слегка отличается в том случае, когда вызов асинхронного метода и оператор await располагаются в разных строках.

  11. В методе ProcessAsync замените строку с оператором await показанным ниже кодом. Первоначальная строка с оператором await является сокращенной формой записи двух следующих строк.

    Dim theTask = DoSomethingAsync()
    Dim result = Await theTask
    
    var theTask = DoSomethingAsync();
    var result = await theTask;
    
  12. Несколько раз нажмите клавишу F11 или в строке меню выберите Отладка, Шаг с заходом, чтобы начать выполнение приложения в пошаговом режиме.

    На строке с вызовом метода DoSomethingAsync команда Шаг с заходом останавливает выполнение внутри метода DoSomethingAsync, тогда как команда Шаг с обходом, как и ожидалось, переходит к следующему оператору. На строке с оператором await обе команды останавливают выполнение на операторе, следующем за оператором await.

Шаг с выходом

При выполнении команды Шаг с выходом в синхронном методе выполнение останавливается в вызывающем методе на строке кода, следующей за вызовом метода. В случае асинхронного метода место остановки выполнения в вызывающем методе определяется более сложным образом и зависит от того, находится ли команда Шаг с выходом в первой строке асинхронного метода.

  1. Замените код в консольном приложении следующим кодом:

    ' Step Out Example 
    Imports System.Threading.Tasks
    
    Module Module1
        Sub Main()
            ProcessAsync.Wait()
        End Sub
    
        Async Function ProcessAsync() As Task
            Dim theTask = DoSomethingAsync()
            Dim z = 0
            Dim result = Await theTask
        End Function
    
        Async Function DoSomethingAsync() As Task(Of Integer)
            Debug.WriteLine("before")  ' Step Out from here
            Await Task.Delay(1000)
            Debug.WriteLine("after")
            Return 5
        End Function 
    End Module
    
    // Step Out Example. 
    using System.Diagnostics;
    using System.Threading.Tasks;
    
    class Program
    {
        static void Main(string[] args)
        {
            ProcessAsync().Wait();
        }
    
        static async Task ProcessAsync()
        {
            var theTask = DoSomethingAsync();
            int z = 0;
            var result = await theTask;
        }
    
        static async Task<int> DoSomethingAsync()
        {
            Debug.WriteLine("before");  // Step Out from here
            await Task.Delay(1000);
            Debug.WriteLine("after");
            return 5;
        }
    }
    
  2. Установите точку останова на строке Debug.WriteLine("before") в методе DoSomethingAsync.

    Это позволит увидеть, как ведет себя команда Шаг с выходом при ее выполнении из строки, не являющейся первой строкой асинхронного кода.

  3. Нажмите клавишу F5 или в строке меню выберите Отладка, Начать отладку, чтобы начать выполнение приложения.

    Выполнение кода останавливается на строке Debug.WriteLine("before") внутри метода DoSomethingAsync.

  4. Нажмите клавиши Shift+F11 или в строке меню выберите Отладка, Шаг с выходом.

    Приложение останавливается в вызывающем методе на операторе await, который относится к задаче, возвращаемой вызываемым методом. То есть приложение останавливается на операторе await в методе ProcessAsync. Приложение не останавливается на строке кода Dim z = 0 (в Visual Basic) или int z = 0; (в C#), расположенной после вызова метода DoSomethingAsync.

  5. В строке меню выберите Отладка, Остановить отладку, чтобы остановить выполнение приложения.

    В следующей части процедуры будет показано, что происходит, когда команда Шаг с выходом выполняется из самой первой строки асинхронного кода.

  6. Удалите существующую точку останова и добавьте точку останова в первую строку метода DoSomethingAsync.

    В C# добавьте точку останова в строку с открывающей фигурной скобкой метода DoSomethingAsync. В Visual Basic добавьте точку останова в строку, которая содержит Async Function DoSomethingAsync() As Task(Of Integer).

  7. Нажмите клавишу F5, чтобы запустить приложение.

    Выполнение кода останавливается на первой строке метода DoSomethingAsync.

  8. В строке меню выберите Отладка, Окна, Выходные данные.

    Откроется окно Выходные данные.

  9. Нажмите клавиши Shift+F11 или в строке меню выберите Отладка, Шаг с выходом.

    Выполнение возобновляется и продолжается до тех пор, пока не оказывается достигнут первый оператор await в асинхронном методе, после чего выполнение останавливается на операторе вызова в вызывающем методе. То есть приложение останавливается на строке Dim the Task = DoSomethingAsync() (в Visual Basic) или var theTask = DoSomethingAsync(); (в C#). При этом в окне вывода появляется сообщение "прежде" ("before"), а сообщение "после" ("after") еще не появляется.

  10. Нажмите клавишу F5, чтобы продолжить выполнение приложения.

    Выполнение возобновляется с оператора, расположенного после оператора await в вызываемом асинхронном методе (DoSomethingAsync). В окне "Выходные данные" отображается сообщение "после" ("after").

См. также

Основные понятия

Запуск, приостановка, шаг, последовательное выполнение и остановка отладки в Visual Studio