#c# #asynchronous #async-await #deadlock
Вопрос:
Я пытаюсь понять концепцию между GetAwaiter().GetResult()
. Я знаю, что его не следует использовать, если это возможно, но я не могу использовать async/await в этом случае, поэтому я должен получить результат от асинхронной функции в синхронном вопросе.
Давайте рассмотрим это простое серверное приложение Blazor:
@page "/"
<button @onclick="GetAwaiterGetResultOnTask">Task GetAwaiter().GetResult()</button>
<button @onclick="GetAwaiterGetResultOnFunction">Function GetAwaiter().GetResult()</button>
<p>@_message</p>
@code {
private string _message;
private async Task<string> AsyncFunction(string message)
{
await Task.Delay(500);
return message;
}
private void GetAwaiterGetResultOnTask()
{
_message = Task.Run(() => AsyncFunction("Message from GetAwaiterGetResultOnTask")).GetAwaiter().GetResult();
}
private void GetAwaiterGetResultOnFunction()
{
_message = AsyncFunction("Message from GetAwaiterGetResultOnFunction").GetAwaiter().GetResult();
}
}
Вызов функции GetAwaiterGetResultOnFunction
приведет к взаимоблокировке. Однако, когда я звоню GetAwaiterGetResultOnTask
, это не так.
В чем ключевое различие между этими функциями? Почему это не приводит к тупику при вызове Task.Run
?
Комментарии:
1. Потому
GetAwaiterGetResultOnFunction
что выполняется в вашем потоке , в тоGetAwaiterGetResultOnTask
время как выполняется в другом.2. Кстати, оба звонка плохие. Вместо этого сделайте методы асинхронными. Есть причина, по которой сервер Blazor широко использует асинхронные методы. Каждый вызов, изменяющий состояние и пользовательский интерфейс, является асинхронным вызовом с сервера в браузер. Блокируя таким образом, вы тратите потоки, которые можно было бы использовать для обслуживания других запросов
3.
I cannot use async/await in this case
почему? Что это за дело? Blazor, особенно сервер Blazor,async
широко используется до такой степени, что не использовать асинхронность сложнее, чем использовать ее. Почему бы вам не использовать асинхронный обратный вызов событий? Документы по обработке событий показывают именно это:button @onclick="UpdateHeading"
иprivate async Task UpdateHeading()
4. Это сообщение в блоге может пролить некоторый свет на проблему: Не блокируйте асинхронный код
5. Асинхронная проверка еще не поддерживается в ASP.NET Ядро. Вот почему я не могу использовать асинхронное ожидание.
Ответ №1:
await
осознает то, что называется SynchronizationContext
. Если такой контекст присутствует ( SynchronizationContext.Current
не равен нулю), то продолжение после await
передается в этот контекст, потому что он должен лучше знать, как с ним обращаться (если вы явно не скажете этого не делать, используя await someTask.ConfigureAwait(continueOnCapturedContext: false)
)
Большинству платформ пользовательского интерфейса не нравится, когда к пользовательскому интерфейсу одновременно обращаются из нескольких потоков, потому что это часто приводит к различным трудным для отладки проблемам. Они используются SynchronizationContext
для обеспечения единого потока выполнения. Для этого они обычно помещают обратные вызовы, отправленные в SynchronizationContext, в какую — то очередь и выполняют их один за другим в одном потоке «пользовательского интерфейса».
Блейзор тоже так делает. GetAwaiterGetResultOnFunction
ваш случай выполняется в этом потоке «пользовательский интерфейс», с SynchronizationContext
доступным. Когда выполнение достигает await Task.Delay
внутренней AsyncFunction
части — фиксируется текущий контекст, и отмечается, что по Task.Delay
завершении — остальная часть функции должна быть отправлена в контекст для выполнения.
Затем вы блокируете поток «Пользовательский интерфейс», выполнив GetResult
задачу, возвращенную из AsyncFunction
. Затем Task.Delay
завершается, и остальная часть функции отправляется в SynchronizationContext
Blazor для выполнения. Однако для этого Blazor нужен тот же поток пользовательского интерфейса, который вы только что заблокировали, что приводит к взаимоблокировке.
Когда вы вызываете GetAwaiterGetResultOnTask
— AsyncFunction
не запускается в потоке «Пользовательский интерфейс» Blazor — он запускается в каком-либо другом потоке (пул потоков). SynchronizationContext
равно нулю, часть после await Task.Delay
будет выполняться в каком-либо потоке пула потоков и для завершения не потребуется поток «пользовательский интерфейс». Тогда нет никакого тупика.