Почему в одном случае GetAwaiter() блокирует мое приложение, а в другом приложение не блокируется?

У меня есть 2 проекта, оба используют Net 5, Entity Framework Net 5 и async. Единственное отличие состоит в том, что заблокированный проект использует Sql Server, а другой использует Sqlite. Но я думаю, что база данных не причина.

Проект 1, который заблокирован:

public void Main()
{
    SqlServerEFNet5 miRepository = new SqlServerEFNet5();
    miRepository.GetAllMyRowsAsync().GetAwaiter().GetResult();
}


public Task<List<MyType> GetAllMyRowsAsync()
{
    using (ContextEfNet5 dbContext = new ContextEfNet5(_optionsDbContext))
    {
        return await dbContext.MyEntities.FromSqlRaw("Select * from MyType").ToListAsync().ConfigureAwait(false);
    }
}

Мой проект 2, он не заблокирован:

private void Main()
{
    MyViewModelProperty.AddRange(Service.Service.GetAllMyRowsAsync().GetAwaiter().GetResult()));
}


public static async Task<List<MyType>> GetAllMyRowsAsync()
{
    using (Context myDbContext = new Context())
    {
        return await myDbContext.MyType.ToListAsync().ConfigureAwait(false);
    }
}

Действительно, в моем втором проекте я не использую необработанный sql-запрос, но я пытался использовать только чистый linq в своем первом проекте, и он все еще заблокирован.

Кроме того, в моем первом проекте я не заполняю коллекцию модели представления, на которую это может повлиять, потому что она используется представлением, но во втором проекте я заполняю коллекцию модели представления, поэтому это не проблема.

Не понимаю, почему в одном случае блокируется, а в другом нет.

Редактировать 01:

Если я использую этот код в проекте 1, он не блокируется:

        Task<int> miTask = Task<int>.Run(() => { return 0; });

        int myIntResult = miTask.GetAwaiter().GetResult();

Редактировать 02: предложение решения другого вопроса мне не помогает, потому что оно предлагает использовать ожидание, но я не могу его использовать, потому что мне нужно вызвать этот асинхронный код в конструкторе, а конструктор не может быть асинхронным.

Редактировать 03: я нашел в этом Как мне синхронно запустить асинхронный метод Task<T>? помощник, который работает.

Один проект работает в среде с SynchronizationContext, а другой без?

GSerg 10.12.2020 16:26

Почему бы вам просто не использовать async Task Main() и не избавиться от GetAwaiter().GetResult()?

Peter Csala 10.12.2020 16:26

Я проверю среду с SynchronizationContext. Но ни в одном из проектов я не устанавливал это. О том, почему не используйте асинхронную задачу, это потому, что я использую этот код в конструкторе класса и в других методах, которые являются обработчиками событий и не могут быть асинхронными.

Álvaro García 10.12.2020 16:37

@GSerg в ссылке, которую вы мне дали, он говорит, что я могу использовать GetAwaiter() .GetResult(), не беспокоясь о взаимоблокировках, но это то, что я делаю в обоих случаях.

Álvaro García 10.12.2020 16:40

@Liam Спасибо за предложение, но в моем случае оно не помогает. В обоих проектах я использую GetAwaiter() в конструкторе основного класса, и в первом случае он заблокирован, а во втором нет.

Álvaro García 10.12.2020 17:11

Я предполагаю, что это может быть связано с тем, что один проект не имеет ConfigureAwait(false) полностью. Не могли бы вы в первом проекте проверить, работает ли это, если вы добавите это в начале метода GetAllMyRowsAsync: await Task.Delay(50).ConfigureAwait(false);? (Пожалуйста, не используйте это как решение, если оно работает, это просто проверка для подтверждения проблемы). Важно, чтобы это было сделано на старте, до другого async звонка!

user10608418 10.12.2020 17:13

Какой из них использует sqlLite, а какой Sql Server? На самом деле это связано с тем, что поставщики EF для них разные. шаблон, основанный на событиях, всегда будет захватывать контекст. Прочтите это — learn.microsoft.com/en-us/archive/msdn-magazine/2015/july/… Раздел The Blocking Hack. И причина спросить, какой из них - я хочу найти исходный код провайдера, чтобы доказать свою точку зрения.

Nikita Chayka 10.12.2020 17:18

@NikitaChayka Первый заблокированный проект использует Sql Server, второй работающий проект использует Sqlite.

Álvaro García 10.12.2020 17:38

@AlvaroGarcía, посмотри мой ответ

Nikita Chayka 10.12.2020 17:57
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
9
399
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Хорошо, разобрался. Тупик возникает из-за SynchronizationContext, и он фиксируется, как неоднократно говорилось в комментариях к вашему вопросу. Обычно решение состоит в том, чтобы либо использовать await до упора, либо ConfigureAwait(false), что вы и делаете. НО!

Как указано здесь (раздел The Blocking Hack): https://learn.microsoft.com/en-us/archive/msdn-magazine/2015/july/async-programming-brownfield-async-development

Если где-то в будущем Microsoft/EF использует преобразование из старого асинхронного шаблона, основанного на событиях, то ConfigureAwait(false) не будет иметь никакого эффекта, потому что контекст в любом случае уже будет захвачен.

И оказывается они делают такое преобразование, вот исходник SqlCommand для SqlServer:

https://github.com/dotnet/SqlClient/blob/bd89b6433892214eed41e97afb0a9430d11d4681/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs

Найдите там строку 2996.

Теперь, почему SqlLite работает нормально - поскольку SqlLite не поддерживает асинхронный ввод-вывод, поэтому все асинхронные операции для SqlLite являются поддельными, вы могли ясно видеть это в источнике SqliteCommand:

https://github.com/dotnet/efcore/blob/main/src/Microsoft.Data.Sqlite.Core/SqliteCommand.cs

Строка 406.

А объяснение «подделки» здесь — https://learn.microsoft.com/en-us/dotnet/standard/data/sqlite/async

Другие вопросы по теме