проблема с асинхронным ожиданием в 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 в моем сервисном слое:

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

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

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

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

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

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

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

Методы, отмеченные как «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
Является ли этот КБ законным? Да, узнайте, почему