проблема с асинхронным ожиданием в EF 6

async-await entity-framework entity-framework-6 wpf

Вопрос

Я пытаюсь выполнить асинхронное программирование с помощью сущности-framework 6 (сначала код) + WPF, и я не вижу, почему пользовательский интерфейс все еще зависает после того, как я сделал асинхронный код. Вот что я делаю с первой строки:

сначала есть обработчик событий, реагирующий на кнопку щелчка:

private async void LoginButton_Click(object sender, RoutedEventArgs e) {
  if (await this._service.Authenticate(username.Text, password.Password) != null)
    this.Close();
}

Затем у меня есть метод Authenticate в моем сервисном слое:

public async Task<User> Authenticate(string username, string password) {
  CurrentUser = await this._context.GetUserAsync(username.ToLower().Trim(), password.EncryptPassword());
  return CurrentUser;
}

и в конце это код EF в контексте:

public async Task<User> GetUserAsync(string username, string password) {
  return await this.People.AsNoTracking().OfType<User>().FirstOrDefaultAsync(u => u.Username == username && u.Password == password);
}

Обновление: после некоторого отслеживания причина замораживания пользовательского интерфейса оказалась процессом инициализации. Блоки потока пользовательского интерфейса до тех пор, пока контекст EF не будет инициализирован, и как только это будет сделано, фактический процесс запроса / сохранения выполняется асинхронно.

Обновите вывод Debug после вызова Task.Yield () в начале обработчика кликов:

53:36:378 Calling Task.Yield
53:36:399 Called Task.Yield
53:36:400 awaiting for AuthenticateAsync
53:36:403 awaiting for GetUserAsync
'MyApp.vshost.exe' (Managed (v4.0.30319)): Loaded 'C:\Windows\Microsoft.Net\assembly\GAC_32\System.Transactions\v4.0_4.0.0.0__b77a5c561934e089\System.Transactions.dll', Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
'MyApp.vshost.exe' (Managed (v4.0.30319)): Loaded 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Numerics\v4.0_4.0.0.0__b77a5c561934e089\System.Numerics.dll', Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
'MyApp.vshost.exe' (Managed (v4.0.30319)): Loaded 'C:\Windows\Microsoft.Net\assembly\GAC_32\System.Data.OracleClient\v4.0_4.0.0.0__b77a5c561934e089\System.Data.OracleClient.dll', Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
'MyApp.vshost.exe' (Managed (v4.0.30319)): Loaded 'D:\SkyDrive\Works\MyApp\MyApp.UI.WPF.Shell\bin\Debug\EntityFramework.SqlServer.dll'
'MyApp.vshost.exe' (Managed (v4.0.30319)): Loaded 'C:\Windows\Microsoft.Net\assembly\GAC_32\System.EnterpriseServices\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.EnterpriseServices.dll', Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
'MyApp.vshost.exe' (Managed (v4.0.30319)): Loaded 'C:\Windows\Microsoft.Net\assembly\GAC_32\System.EnterpriseServices\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.EnterpriseServices.Wrapper.dll', Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
'MyApp.vshost.exe' (Managed (v4.0.30319)): Loaded 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Runtime.Serialization\v4.0_4.0.0.0__b77a5c561934e089\System.Runtime.Serialization.dll', Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
'MyApp.vshost.exe' (Managed (v4.0.30319)): Loaded 'EntityFrameworkDynamicProxies-MyApp.Model.Domain.People'
'MyApp.vshost.exe' (Managed (v4.0.30319)): Loaded 'EntityFrameworkDynamicProxies-MyApp.Model.Domain.Security'
53:39:965 Out of GetUserAsync
53:39:968 out of AuthenticateAsync
The thread '<No Name>' (0x1e98) has exited with code 0 (0x0).
The thread '<No Name>' (0x17d4) has exited with code 0 (0x0).
The thread '<No Name>' (0x175c) has exited with code 0 (0x0).
The thread '<No Name>' (0x220) has exited with code 0 (0x0).
The thread '<No Name>' (0x1dc8) has exited with code 0 (0x0).
The thread '<No Name>' (0x1af8) has exited with code 0 (0x0).

Принятый ответ

Методы, отмеченные как «async», по-прежнему синхронны до момента, когда происходит первый «ожидание». Из-за этого, если какое-либо из того, что происходит в этом исходном коде, занимает слишком много времени (200 мс или более, я думаю, это руководство для WinRT, что кажется разумным), тогда вы можете заставить код быстрее возвращаться, вставив ожидаемый ранее.

Например, в вашем LoginButton_Click вы можете вставить первую строку « Ожидать Task.Yield ()», которая позволит вызову быстрее вернуться к потоку пользовательского интерфейса.

Теперь, только с этими изменениями, все методы будут выполняться в потоке пользовательского интерфейса из-за поведения async / await. Мне все равно нравится делать это изменение в первую очередь, потому что во многих случаях это то, что на самом деле ожидает пользователь (модификатор «async» является чем-то путающим в этом отношении), и это то, что вы можете сделать в начале обработчика без необходимости с вещами далее вниз стек.

Следующий шаг, который мы можем сделать, если вышеуказанное недостаточно (например, инициализация контекста занимает слишком много времени, все еще происходит в потоке пользовательского интерфейса и все еще зависает от пользовательского интерфейса, только в несколько иной момент времени) принимает те части, t должно произойти в потоке пользовательского интерфейса и позволить дождаться, что они могут обрабатываться на любом потоке, а не только на потоке пользовательского интерфейса. В общем, это хорошая практика для реагирования, даже в сценариях, где код в настоящее время работает «достаточно быстро», чтобы не быть заметной проблемой.

Для этого мы используем add ConfigureAwait (false) для задачи.

  • Метод GetUserAsync должен добавить его («цепочка») после вызова FirstOrDefaultAsync
    • альтернативно, и IMHO немного чище - просто избавиться от ключевых слов async / wait в методе GetUserAsync и просто вернуть задачу, которую вы возвращаете из FirstOrDefaultAsync. Асинхронный / ждущий на самом деле не «покупает» что-либо в этом методе как есть, ИМХО :)
  • в Authenticate, вероятно, вы должны добавить его после вызова GetUserAsync
    • один потенциальный «getcha» здесь, о котором я не уверен, - это то, что CurrentUser привязан к данным в пользовательском интерфейсе. Поскольку он является членом службы _, я предполагаю, что это не так, но даже если это так, я думаю, что WPF в порядке с обновляемыми данными, которые обновляются в потоках, отличных от UI, и он связан с перенаправлением изменений обратно в пользовательский интерфейс (диспетчер?) нить. Это отличается от фреймворков, таких как Silverlight, где обновление свойства в не-пользовательском интерфейсе, связанное с данными в пользовательском интерфейсе, приведет к такому же перекрестному потоку, как если бы вы вручную обновили целевой элемент управления. Если я ошибаюсь в этом и 1) CurrentUser привязан к данным в вашем пользовательском интерфейсе и 2), что обновление данных, связанное с потоком не-UI, вызывает исключение во время выполнения, а затем избегает добавления ConfigureAwait (false) в этом методе , Извините за длину этого, просто пытаюсь связать то, что я немного не уверен в этой конкретной модификации. :)
  • в LoginButton_Click, мы НЕ должны добавлять его, потому что остальная часть метода (this.Close) должна произойти в потоке пользовательского интерфейса, а ConfigureAwait (false) здесь нарушит это

Как только оба этих изменения будут включены, вы оба: 1) возвращаете управление обратно вызывающему абоненту так быстро, как можете (выполняете минимальный объем кода, синхронный с обработчиком событий) и 2) выполняете работу, которая не нужна быть в потоке пользовательского интерфейса на других потоках, что, надеюсь, означает, что ваш пользовательский интерфейс больше не «замораживается».

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

Удачи!



Лицензировано согласно: CC-BY-SA with attribution
Не связан с Stack Overflow
Является ли этот КБ законным? Да, узнайте, почему
Лицензировано согласно: CC-BY-SA with attribution
Не связан с Stack Overflow
Является ли этот КБ законным? Да, узнайте, почему