我正在尝试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).
答案 0 :(得分:5)
标记为“异步”的方法仍然是同步的,直到第一次“等待”发生时为止。因此,如果初始代码中发生的任何事情花费的时间太长(我认为200毫秒或更长时间是WinRT的准则,这似乎是合理的),那么您可能希望通过先插入await来强制代码更快地返回。
例如,在您的LoginButton_Click中,您可以插入第一行'await Task.Yield()',这将允许调用更快地返回到UI线程。
现在,仅通过该更改,由于async / await行为,这些方法仍将在UI线程上运行。我仍然喜欢首先进行更改,因为在很多情况下,这是用户实际期望发生的事情('async'修饰符在这方面有点令人困惑),而且你可以在处理程序的开头做一些事情而不必弄乱随着堆栈的进一步发展。
如果上面的内容不足(如上下文初始化花费太长时间,仍然在UI线程上发生,并且仍然冻结UI,只是在稍微不同的时间点)我们可以做的下一步就是采取这些部分不需要在UI线程上发生,让等待知道它们可以在任何线程上处理,而不仅仅是UI线程。对于响应性而言,这通常是一种很好的做法,即使在代码当前运行“足够快”而不是明显问题的情况下也是如此。
为此,我们使用添加ConfigureAwait(false)到任务。
一旦这两个更改都进入,您将1)尽快将控制权返回给调用者(执行与事件处理程序同步的最少量代码)和2)执行不做的工作不需要在其他线程的UI线程上,这应该意味着你的UI不再“冻结”。
如果在更改后它仍然冻结,您可能只需要在调试器下运行它,当它冻结时,中断以查看UI线程的堆栈是什么来查找有问题的代码。 :)
祝你好运!