Я изучаю async / await и столкнулся с ситуацией, когда мне нужно синхронно вызывать асинхронный метод. Как я могу это сделать?
Лучший ответ - Вы не , детали зависят от ситуации.
Является ли это свойство геттером / сеттером? В большинстве случаев лучше иметь асинхронные методы, чем "асинхронные свойства". (Для получения дополнительной информации см. мой пост в блоге об асинхронных свойствах ).
Это приложение MVVM, и вы хотите выполнить асинхронное связывание данных? Затем используйте что-то вроде моего NotifyTask
, как описано в моей статье MSDN об асинхронном связывании данных .
Это конструктор? Тогда вы, вероятно, захотите рассмотреть асинхронный фабричный метод. (Подробнее см. В моем блоге об асинхронных конструкторах ).
Почти всегда лучший ответ, чем синхронизация по асинхронности.
Если это невозможно для вашей ситуации (и вы знаете это, задавая вопрос здесь , описывающий ситуацию ), то я бы рекомендовал просто использовать синхронный код. Асинхронный полностью лучше; Синхронизация полностью на втором месте. Синхронизация по асинхронности не рекомендуется.
Однако есть несколько ситуаций, когда необходима синхронизация по асинхронности. В частности, вы ограничены вызывающим кодом, так что у вас есть для синхронизации (и у вас нет абсолютно никакого способа переосмыслить или реструктурировать ваш код для обеспечения асинхронности), и у вас есть для вызова асинхронного кода. Это очень редкая ситуация, но она возникает время от времени.
В этом случае вам понадобится один из хаков, описанных в моей статье о brownfield async
development , а именно:
- Блокировка (например,
GetAwaiter().GetResult()
). Обратите внимание, что это может вызвать взаимные блокировки (как я описал в моем блоге).
- Запуск кода в потоке пула потоков (например,
Task.Run(..).GetAwaiter().GetResult()
). Обратите внимание, что это будет работать только в том случае, если асинхронный код может выполняться в потоке пула потоков (т. Е. Не зависит от контекста пользовательского интерфейса или ASP.NET).
- Вложенные циклы сообщений. Обратите внимание, что это будет работать только в том случае, если асинхронный код принимает только однопоточный контекст, а не определенный тип контекста (большая часть кода пользовательского интерфейса и ASP.NET ожидает определенного контекста).
Вложенные циклы сообщений являются наиболее опасными из всех хаков, поскольку они вызывают повторный вход . Повторный вход чрезвычайно сложен, и (IMO) является причиной большинства ошибок приложений в Windows. В частности, если вы находитесь в потоке пользовательского интерфейса и блокируете рабочую очередь (ожидая завершения асинхронной работы), то CLR фактически выполняет для вас некоторую перекачку сообщений - он фактически обрабатывает некоторые сообщения Win32 из вашего кода . О, и вы не знаете, какие сообщения - когда Крис Брамм говорит: «Разве не было бы замечательно точно знать, что будет накачано? К сожалению, накачка - это черное искусство, которое находится за пределами понимания смертных. ", тогда у нас действительно нет надежды узнать.
Итак, когда вы блокируете таким образом в потоке пользовательского интерфейса, у вас возникают проблемы. Другая цитата cbrumme из той же статьи: «Время от времени клиенты внутри или за пределами компании обнаруживают, что мы перекачиваем сообщения во время управляемой блокировки в STA [поток пользовательского интерфейса]. Это законное беспокойство, потому что они знают, что это очень сложно написать код, устойчивый к повторному входу. "
Да, это так. Очень трудный для написания код, устойчивый к повторному входу. И вложенные циклы сообщений заставляют писать код, устойчивый к повторному входу. Вот почему принятый (и наиболее одобренный) ответ на этот вопрос чрезвычайно опасен на практике.
Если у вас полностью отсутствуют другие возможности - вы не можете изменить свой код, вы не можете реструктурировать его так, чтобы он был асинхронным - вас заставляет неизменный вызывающий код синхронизировать - вы не можете изменить нижестоящий код для синхронизации- вы не можете заблокировать - вы не можете запустить асинхронный код в отдельном потоке - тогда и только тогда , если вы решите принять повторный вход.
Если вы окажетесь в этом углуЯ бы порекомендовал использовать что-то вроде Dispatcher.PushFrame
для приложений WPF , цикл с Application.DoEvents
для приложений WinForm, а для общего случая мой собственный AsyncContext.Run
.