Подтвердить что ты не робот

Что происходит с "ожидающим" потоком в С# Async CTP?

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

Предположим, что у меня есть вызов await во вложенной функции основного потока пользовательского интерфейса. Что происходит с нитью в этот момент? Возвращает ли управление в контур сообщения, и поток пользовательского интерфейса может обрабатывать другие входы?

Когда ожидаемая задача завершается, весь стек попадает в очередь сообщений, так что элемент управления будет возвращаться через каждую из этих вложенных функций или что-то еще происходит здесь?

И во-вторых (пока я обращаю ваше внимание), я действительно не понимаю, почему асинхронные методы должны быть помечены async. Не может ли какой-либо метод выполняться асинхронно? Что делать, если я хочу выполнить метод асинхронно, но у него нет ключевого слова async - есть ли способ сделать это просто?

Приветствия.:)

Edit: По общему признанию, если бы я мог получить компиляцию кода образца, я мог бы, вероятно, просто понять это сам, но по той или иной причине я столкнулся с этим блоком. То, что я действительно хочу знать, в какой степени продолжение продолжается... Замораживает весь стек вызовов, возобновляет его, когда задача завершается, или она только возвращается? Должна ли сама функция быть помечена как асинхронная, чтобы поддерживать продолжение, или (как я спросил первоначально) продолжает ли она весь стек вызовов?

Если он не затормозит весь стек вызовов, что произойдет, когда асинхронное ожидание ударит по неасинхронной функции вызова? Блокирует ли это? Разве это не победит точку ожидания? Надеюсь, вы увидите, что мне не хватает понимания здесь, и я надеюсь, что кто-то сможет заполнить его, чтобы я мог продолжить изучение этого.

4b9b3361

Ответ 1

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

Да. Когда вы await ожидаете (например, Task<TResult>), фиксируется текущая позиция потока в методе async. Затем он ставит в очередь оставшуюся часть метода ( "продолжение" ), который должен быть выполнен, когда завершено это ожидание (например, когда завершается Task<TResult>).

Однако есть оптимизация, которая может произойти: если ожидаемое уже завершено, то await не нужно ждать, и он просто продолжает выполнение метода немедленно. Это называется "быстрый путь", описанный здесь.

Когда ожидаемая задача завершается, весь стек попадает в очередь сообщений, так что элемент управления будет возвращаться через каждую из этих вложенных функций или что-то еще происходит здесь?

Текущая позиция потока помещается в очередь сообщений пользовательского интерфейса. Детали немного сложнее: продолжения запланированы на TaskScheduler.FromCurrentSynchronizationContext, если SynchronizationContext.Current не равно null, и в этом случае они запланированы на TaskScheduler.Current. Кроме того, это поведение можно переопределить вызовом ConfigureAwait(false), который всегда планирует продолжение в пуле потоков. Так как SynchronizationContext.Current является UI SynchronizationContext для WPF/WinForms/Silverlight, это продолжение выводится в очередь сообщений пользовательского интерфейса.

И во-вторых (пока я обращаю ваше внимание), я действительно не понимаю, почему асинхронные методы должны быть помечены как асинхронные. Не может ли какой-либо метод выполняться асинхронно? Что делать, если я хочу выполнить метод асинхронно, но у него нет ключевого слова async - есть ли способ сделать это просто?

Это несколько разные значения слова "асинхронный". Ключевое слово async включает ключевое слово await. Другими словами, методы async могут await. Старомодные асинхронные делегаты (т.е. BeginInvoke/EndInvoke) сильно отличаются от async. Асинхронные делегаты выполняются в потоке ThreadPool, но методы async выполняются в потоке пользовательского интерфейса (при условии, что они вызываются из контекста пользовательского интерфейса, и вы не вызываете ConfigureAwait(false)).

Если вы хотите, чтобы в потоке ThreadPool выполнялся (не async) метод, вы можете сделать это следующим образом:

await Task.Run(() => MyMethod(..));

То, что я действительно хочу знать, в какой степени продолжение продолжается... Замораживает весь стек вызовов, возобновляет его, когда задача завершается, или она только возвращается? Должна ли сама функция быть помечена как асинхронная, чтобы поддерживать продолжение, или (как я спросил первоначально) продолжает ли она весь стек вызовов?

Текущая позиция фиксируется и "возобновляется" при продолжении. Любая функция, использующая await для поддержки продолжений, должна быть отмечена async.

Если вы вызываете метод async из метода не async, то вы должны напрямую обращаться к объекту Task. Обычно это не делается. Методы верхнего уровня async могут возвращать void, поэтому нет причин не иметь обработчиков событий async.

Обратите внимание, что async является чисто преобразованием компилятора. Это означает, что методы async являются точно такими же, как обычные методы после их компиляции. Среда выполнения .NET не обрабатывает их каким-либо особым образом.

Ответ 2

Это зависит от поведения Awaitable.

Он имеет возможность запускать синхронно, т.е. запускается в потоке и возвращает управление обратно в awaiter в тот же поток.

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

Что касается вашего второго вопроса, ключевое слово async не зависит от того, вызван ли метод асинхронно или нет, но есть ли тело этого метода, чтобы вызвать сам асинхронный код.

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

Скажем, у вас есть метод

public async Task DoSomething() {
}

когда вы вызываете его из потока пользовательского интерфейса, вы возвращаете задачу. На этом этапе вы можете заблокировать задачу с помощью .Wait() или .Result, которая запускает метод async в своем TaskScheduler или блокирует с помощью .RunSynchronously(), который будет запускать его в потоке пользовательского интерфейса. Конечно, любое ожидание, которое происходит внутри DoSomething, является в основном еще одним продолжением, поэтому может закончиться выполнение части кода в потоке TaskScheduler. Но в конце поток пользовательского интерфейса блокируется до завершения, как обычный синхронный метод.

Или вы можете запланировать продолжение с помощью .ContinueWith(), который создаст действие, которое вызывается TaskScheduler при завершении Задания. Это немедленно вернет управление текущему коду, который продолжает делать то, что он делал в потоке пользовательского интерфейса. Продолжение не захватывает столбец, это просто действие, поэтому он просто захватывает любые переменные, которые он получает из своей внешней области.