Что происходит с ожидающим потоком в C # Async CTP? - PullRequest
23 голосов
/ 12 августа 2011

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

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

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

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

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

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

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

Ответы [ 2 ]

26 голосов
/ 30 августа 2011

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

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

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

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

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

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

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

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

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

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

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

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

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

4 голосов
/ 12 августа 2011

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

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

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

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

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

Допустим, у вас есть метод

public async Task DoSomething() {
}

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

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

...