У меня есть 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>? помощник, который работает.
Почему бы вам просто не использовать async Task Main()
и не избавиться от GetAwaiter().GetResult()
?
Я проверю среду с SynchronizationContext. Но ни в одном из проектов я не устанавливал это. О том, почему не используйте асинхронную задачу, это потому, что я использую этот код в конструкторе класса и в других методах, которые являются обработчиками событий и не могут быть асинхронными.
@GSerg в ссылке, которую вы мне дали, он говорит, что я могу использовать GetAwaiter() .GetResult(), не беспокоясь о взаимоблокировках, но это то, что я делаю в обоих случаях.
@Liam Спасибо за предложение, но в моем случае оно не помогает. В обоих проектах я использую GetAwaiter() в конструкторе основного класса, и в первом случае он заблокирован, а во втором нет.
Я предполагаю, что это может быть связано с тем, что один проект не имеет ConfigureAwait(false)
полностью. Не могли бы вы в первом проекте проверить, работает ли это, если вы добавите это в начале метода GetAllMyRowsAsync
: await Task.Delay(50).ConfigureAwait(false);
? (Пожалуйста, не используйте это как решение, если оно работает, это просто проверка для подтверждения проблемы). Важно, чтобы это было сделано на старте, до другого async
звонка!
Какой из них использует sqlLite, а какой Sql Server? На самом деле это связано с тем, что поставщики EF для них разные. шаблон, основанный на событиях, всегда будет захватывать контекст. Прочтите это — learn.microsoft.com/en-us/archive/msdn-magazine/2015/july/… Раздел The Blocking Hack. И причина спросить, какой из них - я хочу найти исходный код провайдера, чтобы доказать свою точку зрения.
@NikitaChayka Первый заблокированный проект использует Sql Server, второй работающий проект использует Sqlite.
@AlvaroGarcía, посмотри мой ответ
Хорошо, разобрался. Тупик возникает из-за 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:
Найдите там строку 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
Один проект работает в среде с
SynchronizationContext
, а другой без?