我正在尝试使用 Entity Framework 6(代码优先)+ WPF 进行异步等待编程,但我不明白为什么在我使代码异步后 UI 仍然卡住。
这是我从第一行开始做的事情:

首先有一个响应点击按钮的事件处理程序:

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);
}

更新: 经过一番追踪,UI 卡住的原因原来是初始化过程。 UI 线程阻塞,直到 EF 上下文被初始化,一旦完成,实际的查询/保存过程将异步执行。

在单击处理程序开始时调用 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”的方法在第一个“await”发生之前仍然是同步的。因此,如果在初始代码中发生的任何事情花费的时间太长(我认为 200 毫秒或更长时间是 WinRT 的准则,这似乎是合理的),那么您可能希望通过提前插入等待来强制代码更快地返回。

例如,在您的 LoginButton_Click 中,您可以插入第一行“await Task.Yield ()”,这将允许调用更快地返回到 UI 线程。

现在,仅凭这一更改,由于 async/await 行为,这些方法仍将在 UI 线程上运行。我仍然喜欢先进行更改,因为在许多情况下,这是用户实际期望发生的事情(“async”修饰符在这方面有点令人困惑),而且您可以在处理程序开始时执行此操作而不必弄乱与堆栈更进一步的东西。

如果上述内容不充分(比如上下文初始化花费的时间太长,仍然发生在 UI 线程上,并且仍然卡住 UI,只是在一个稍微不同的时间点),我们可以做的下一步是采取那些没有的部分不需要在 UI 线程上发生,并让 await 知道它们可以在任何线程上处理,而不仅仅是 UI 线程。无论如何,这对于响应性来说通常是一个很好的做法,即使在代码当前运行“足够快”而不会引起明显问题的情况下也是如此。

为此,我们使用 add ConfigureAwait (false) 到任务。

  • GetUserAsync 方法应该在 FirstOrDefaultAsync 调用之后添加它(“链接”它)
  • 或者,恕我直言,稍微干净一点,就是去掉 GetUserAsync 方法中的 async/await 关键字,然后返回从 FirstOrDefaultAsync 返回的任务。在这种方法中,异步/等待并没有真正为您“购买”任何东西,恕我直言:)
  • 在 Authenticate 中,您可能应该在 GetUserAsync 调用之后添加它
  • 这里我不确定的一个潜在“问题”是 CurrentUser 是否绑定(bind)到 UI 中。由于它是 _service 的成员,我猜它不是,但即使是,我认为 WPF 可以在非 UI 线程上更新数据绑定(bind)项,并且处理将更改编码回 UI (调度员?)线程。这与 Silverlight 等框架不同,后者更新数据绑定(bind)到 UI 的非 UI 上的属性将导致与手动更新目标控件相同的跨线程故障。如果我对此有误,并且 1) CurrentUser 数据绑定(bind)到您的 UI 和 2) 非 UI 线程上的数据绑定(bind)更新导致运行时异常,则避免在此方法中添加 ConfigureAwait(false) .很抱歉这么长,只是想把我对这个特定修改有点不确定的东西联系起来。 :)
  • LoginButton_Click 中的
  • ,我们不应该添加它,因为方法的其余部分 (this.Close) 需要在 UI 线程上发生,这里的 ConfigureAwait(false) 会破坏

  • 一旦这两个变化都发生了,你们都将 1) 尽快将控制权返回给调用者(与事件处理程序同步执行最少的代码)和 2) 做不需要的工作在其他线程的 UI 线程上,这应该意味着您的 UI 不再“卡住”。

    如果在这些更改后它仍然卡住,您可能只需要在调试器下运行它,当它卡住时,中断以查看 UI 线程的堆栈是什么以找到有问题的代码。 :)

    祝你好运!

    关于wpf - EF 6 中的异步等待问题,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/13249086/

    10-11 22:34
    查看更多