В чем разница между AsyncFunction().GetAwaiter().GetResult() и Task.Run (() = > Функция async).GetAwaiter().GetResult()?

#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 будет выполняться в каком-либо потоке пула потоков и для завершения не потребуется поток «пользовательский интерфейс». Тогда нет никакого тупика.