Странное поведение отладчика в асинхронном методе

Когда я перешагнул точки останова в своем коде, я столкнулся со странным поведением отладчика:

public async Task DoSomeWork()
{
     await Task.Run(() => { Thread.Sleep(1000); });

     var test = false;
     if (test)
     {
          throw new Exception("Im in IF body!");
     }
}

Отладчик переходит в тело if. Примечательно, что исключение на самом деле не выбрасывается, а просто выглядит так. Таким образом, вы не сможете воспроизвести это, если поместите точку останова прямо на throw. Вы должны поместить его выше и спуститься к телу if, чтобы поймать его. То же самое работает с любым экземпляром исключения (а также с явным null) и даже с return вместо throw.

Кроме того, это работает, даже если я удаляю строку с await.

Я пытался запустить этот фрагмент кода с разных компьютеров, так что это не проблема ПК. Также я подумал, что это ошибка в коде VS, и попытался запустить его в Rider от JetBrains - результат тот же.

Я уверен, что это асинхронная вещь, но как это работает явно?


person Altav1sta    schedule 01.03.2017    source источник
comment
Я только что подал отчет об ошибке в Microsoft, включая этот обман и снимок экрана из моей собственной IDE, чтобы доказать, что дизассемблирование и C# указатели команд представления исходного кода не совпадают.   -  person Cee McSharpface    schedule 15.06.2018


Ответы (1)


Ваш код легко воспроизводит проблему в сборке "Debug" с использованием Visual Studio 2015. Мне нужно было только добавить Program.Main() с вызовом DoSomeWork().Wait();, установить точку останова в методе и выполнить его пошагово. .

Что касается того, почему это происходит, то это, несомненно, связано с комбинацией переписываемого метода async и сгенерированной базы данных отладки (.pdb). Подобно методам итераторов, добавление async к методу заставляет компилятор преобразовать ваш метод в конечный автомат. Фактически сгенерированный IL лишь немного похож на исходный метод. То есть, если вы посмотрите на него, вы сможете определить ключевые компоненты исходного кода, но теперь он находится в большом операторе switch, который обрабатывает то, что происходит, когда метод возвращается в каждом операторе await, а затем повторно вводится с завершением. каждого ожидаемого выражения.

Когда кажется, что оператор программы находится в throw, на самом деле он находится в неявном операторе return в методе. Просто база данных отладки для исполняемого файла не предоставляет оператора программы для этой строки.

При отладке есть подсказка, что именно это и происходит. Когда вы перейдете через оператор if, вы заметите, что он переходит прямо к оператору throw. Если бы на самом деле вводился блок операторов if, следующая строка оператора программы фактически была бы открывающей фигурной скобкой для блока, а не оператором программы.

Вы также можете добавить, например. Console.WriteLine() в конце метода, и это даст отладчику достаточно информации для синхронизации и не покажет вам неверный номер строки.

Дополнительные сведения о том, как компилятор обрабатывает методы async, см. в разделе является ли новая асинхронная функция C# строго реализованной в компиляторе, а предоставленные там ссылки (включая серию статей Джона по теме).

person Peter Duniho    schedule 01.03.2017
comment
Вы сказали «добавление async и хотя бы одного await», но я воспроизвел это даже без строки с await вообще - person Altav1sta; 01.03.2017
comment
@ Altav1sta: хорошо, тогда кажется, что компилятор генерирует конечный автомат для всех async методов. Я интерпретировал, что это работает в вашем сообщении, что означает, что отладчик сделал правильно вещь, а не неправильно. Я думаю, это не то, что вы имели в виду. - person Peter Duniho; 01.03.2017
comment
Да, извините за путаницу. Я имел в виду это работает так же, т. е. я ввожу if тело, но исключение на самом деле не выдается - person Altav1sta; 01.03.2017