#.net #asynchronous
#.net #асинхронный
Вопрос:
Программы пользовательского интерфейса (WPF или Windows Forms) и ASP.NET программы должны использовать async
и await
тщательно использовать, чтобы избежать взаимоблокировки. Почему консольная программа не имеет такого же требования? Например, в следующем блокировка on await
в MyGetAsync, а также блокировка at .Result
в MyInvoker не приводит к взаимоблокировке.
static void Main(string[] args)
{
Console.WriteLine("test...");
MyInvoker();
Console.WriteLine("END. Press the Enter key to exit the program.");
Console.ReadLine();
}
internal static void MyInvoker()
{
Task<string> task = MyGetAsync();
string s = task.Resu<
if (s.Contains("doctype") || s.Contains("DOCTYPE"))
Console.WriteLine("received an HTML page");
else
throw new Exception(s);
}
internal static async Task<string> MyGetAsync()
{
using (var httpClient = new HttpClient())
{
string response = await httpClient.GetStringAsync("https://google.com");
return response;
}
}
Ответ №1:
Классическая тупиковая ситуация состоит из двух частей:
- Блокировка асинхронного кода (который используется не
ConfigureAwait(false)
везде). - Асинхронный контекст, который одновременно разрешает только один поток.
Ваш пример кода всегда выполняет (1) в обеих средах. Разница в (2). В частности, приложения пользовательского интерфейса имеют a SynchronizationContext
для своего потока пользовательского интерфейса, поэтому они await
естественным образом возвращаются в поток пользовательского интерфейса после await
завершения. Консольные приложения не имеют такого контекста, поэтому тупиковой ситуации не происходит.
Комментарии:
1. «Контекст» расплывчатый. «Поток», кажется, легче представить как поток управления. В примере кода поток управления передается в «вызывается» при ожидании. Вскоре после этого «вызывающий» блокирует поток управления. Когда «вызываемый» в конечном итоге продолжается, кажется, что у него нет потока для продолжения, потому что «вызывающий» заблокировал поток.
2. @H2ONaCl: «контекст» подробно описан в связанных сообщениях в блоге. Это так
SynchronizationContext.Current
, если этоnull
не так, и в этом случае это такTaskScheduler.Current
.3. Также: не думайте о потоках, когда находитесь в
async
мире; это может ввести в заблуждение. В случае пользовательского интерфейса, да,SynchronizationContext
привязан к определенному потоку пользовательского интерфейса. Однако такая же тупиковая ситуация может возникнуть на ASP.NET предварительное ядро, гдеSynchronizationContext
оно не привязано к определенному потоку, но по-прежнему допускает только один поток за раз .4. Отсутствие определенного контекста мало чем отличается от утверждения, что у него нет проблемы взаимоблокировки, что уже очевидно. Я мог бы возразить: «если не WPF, то используйте magic для работы, несмотря на кажущийся заблокированный метод, ожидающий заблокированного метода».
5. По первой ссылке раздел «что вызывает тупик» кажется более длинным описанием очевидной причины считать, что тупик возможен. Просто более подробное описание того, что я описал здесь. Загадка в том, почему внешний вид обманчив в случае консольных приложений.
Ответ №2:
Вы по-разному взаимодействуете с пользовательским интерфейсом, таким как Windows Forms и WPF, по сравнению с консольным приложением. Обычно вы ждете, пока консольное приложение не скажет вам что-то сделать, прежде чем делать, а затем ждете, пока это не будет сделано. В результате нет необходимости беспокоиться о том, что логика потоков конкурирует с ресурсами пользовательского интерфейса. С Windows Forms и WPF (на самом деле с любым графическим интерфейсом) у вас происходит много событий, о которых вы можете не знать, и людям трудно помнить, что бизнес-логика не входит в поток пользовательского интерфейса, иначе это действительно не было бы большой проблемой. Теперь, много раз, люди делают это, когда они уже написали огромную часть своей программы, прежде чем она окончательно заблокировалась, и в этот момент им приходится пересматривать довольно много, чтобы правильно ее обработать..