EF 6中的async-await问题

时间:2012-11-06 10:30:39

标签: wpf entity-framework async-await entity-framework-6

我正在尝试async-await编程与实体框架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).

1 个答案:

答案 0 :(得分:5)

标记为“异步”的方法仍然是同步的,直到第一次“等待”发生时为止。因此,如果初始代码中发生的任何事情花费的时间太长(我认为200毫秒或更长时间是WinRT的准则,这似乎是合理的),那么您可能希望通过先插入await来强制代码更快地返回。

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

现在,仅通过该更改,由于async / await行为,这些方法仍将在UI线程上运行。我仍然喜欢首先进行更改,因为在很多情况下,这是用户实际期望发生的事情('async'修饰符在这方面有点令人困惑),而且你可以在处理程序的开头做一些事情而不必弄乱随着堆栈的进一步发展。

如果上面的内容不足(如上下文初始化花费太长时间,仍然在UI线程上发生,并且仍然冻结UI,只是在稍微不同的时间点)我们可以做的下一步就是采取这些部分不需要在UI线程上发生,让等待知道它们可以在任何线程上处理,而不仅仅是UI线程。对于响应性而言,这通常是一种很好的做法,即使在代码当前运行“足够快”而不是明显问题的情况下也是如此。

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

  • GetUserAsync方法应该在FirstOrDefaultAsync调用之后添加它(“链”它)
    • 或者,恕我直言,稍微清洁,就是在GetUserAsync方法中删除async / await关键字,只返回从FirstOrDefaultAsync返回的任务。 async / await并不是真的在这个方法中“买”你的任何东西,恕我直言:)
  • 在Authenticate中,您应该在GetUserAsync调用之后添加它
    • 这里有一个潜在的'陷阱',我不确定是否将CurrentUser数据绑定到UI中。因为它是_service的成员,我猜它不是,但即使它是,我认为 WPF可以正常使用非UI线程上的数据绑定项目并且它处理将更改封送回UI(调度程序?)线程。这与像Silverlight这样的框架不同,在非框架中更新数据绑定到UI的UI上的属性将导致相同的跨线程故障,就像您手动更新目标控件一样。如果我错了,1)CurrentUser数据绑定到你的UI 2)非UI线程上的数据绑定更新导致运行时异常,然后避免ConfigureAwait (假)在这种方法中添加。对不起这个问题的长度,只是试着联系我对这个特殊修改有点不确定。 :)
  • 在LoginButton_Click中,我们应该 NOT 添加它,因为方法的其余部分(this.Close)需要在UI线程上发生,而ConfigureAwait(false)在这里会破坏

一旦这两个更改都进入,您将1)尽快将控制权返回给调用者(执行与事件处理程序同步的最少量代码)和2)执行不做的工作不需要在其他线程的UI线程上,这应该意味着你的UI不再“冻结”。

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

祝你好运!