2012-11-06 156 views
3

我想異步等待編程與實體框架6(代碼第一)+ WPF,我不明白爲什麼UI仍然凍結後,我使代碼異步。 下面是我從第一線做什麼:異步等待問題EF 6

首先有一個事件處理程序響應點擊按鈕:

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

然後我有身份驗證方法,在我服務層:

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

據我所知,EF 6.0還沒有發佈,所以基於任務的異步支持沒有完全實現。 – Jehof

+0

也取決於你的底層提供者是否支持它。如果不是,它會回到阻塞狀態。 –

+0

不相關,但是你可以在GetUserAsync中使用OfType ()嗎? –

回答

5

方法標記爲「異步」仍然是同步的,直到在第一個「等待點' 發生。因此,如果在初始代碼中發生的任何事情需要很長時間(200ms或更多,我認爲是WinRT的指導方針,這似乎是合理的),那麼您可能希望通過提前插入await來強制代碼返回更快。

例如,在您的LoginButton_Click中,您可以插入第一行'await Task.Yield()',這將允許調用更快地返回到UI線程。

現在,僅憑這種改變,由於異步/等待行爲,這些方法仍然會在UI線程上運行。我仍然喜歡首先做出這種改變,因爲在很多情況下,這是用戶實際期望發生的事情('async'修飾符在這方面有點混淆),而且這是在處理程序開始時您可以做的事情,而不必亂七八糟隨着事情進一步下降。

如果上述內容不夠充分(如上下文初始化時間過長,仍然發生在UI線程上,並且仍然凍結UI,只是在稍微不同的時間點上),我們可以執行下一步操作不需要在UI線程上發生的部分,並讓他們知道它們可以在任何線程上處理,而不僅僅是UI線程。無論如何,對於響應能力來說,這通常是一種很好的做法,即使在代碼目前運行速度夠快而不是明顯問題的情況下也是如此。

爲此,我們在任務中使用add ConfigureAwait(false)。

  • GetUserAsync方法應該添加它(「鏈」吧)FirstOrDefaultAsync呼叫
    • 另外,恕我直言稍微乾淨後,是剛剛擺脫異步電動機/等待在GetUserAsync方法,只是回報的關鍵字您從FirstOrDefaultAsync返回的任務。異步/的await是不是真的「買」你這個方法是什麼 - 是,恕我直言:)
  • 在身份驗證
  • ,你應該GetUserAsync調用後
    • 的一個潛在的「極有可能加重它的疑難雜症'我不確定的是CurrentUser是否被數據綁定到UI中。由於它是_service的成員,我猜這不是,但即使它是,我認爲 WPF很好,數據綁定項目正在非UI線程上更新,並且它處理編組回到UI(調度程序?)線程。這與Silverlight這樣的框架不同,在Silverlight中更新數據綁定到UI的非UI的屬性會導致相同的跨線程故障,就像您手動更新目標控件一樣。如果我錯了,1)CurrentUser被數據綁定到你的UI 2)非UI線程上的數據綁定更新導致運行時異常,那麼避免將ConfigureAwait(false)添加到這種方法。對不起,這只是試圖說明我對這個特定的修改有點不確定。 :)
  • 在LoginButton_Click,我們應該添加它,因爲方法(this.Close)的其餘部分需要在UI線程上發生,並ConfigureAwait(假)這裏將打破

一旦這兩方面的變化是,你們雙方1)儘可能快控制權返回給調用者,你可以(做代碼同步的最少的事件處理程序)和2)可以這樣做,沒有按工作不需要在其他線程的UI線程上,這應該意味着你的UI不會「凍結」。

如果它們在這些更改後仍然凍結,那麼您可能只需要在調試器下運行它,當它凍結時,請打破查看UI線程的堆棧以查找有問題的代碼。 :)

祝你好運!

+2

相反Task.Yield的這把我的軌道上的解決方案,真正解耦GUI線程我不得不這樣做:等待Task.Delay(TimeSpan.FromMilliseconds(1))ConfigureAwait(假)。強制收益,但也繼續在不同的線程。我不能告訴task.yield它應該從現在開始使用不同的線程。我不禁想知道是否有一種更好的方式,而不是基本上延遲它可能會有多低 –