Sto provando una programmazione asincrona con entity-framework 6 (code first) + WPF e non riesco a capire perché l'UI si blocchi ancora dopo aver reso il codice asincrono. Ecco cosa sto facendo fin dalla prima riga:
prima c'è un gestore di eventi che risponde a un pulsante clic:
private async void LoginButton_Click(object sender, RoutedEventArgs e) {
if (await this._service.Authenticate(username.Text, password.Password) != null)
this.Close();
}
Poi ho il metodo di autenticazione nel mio livello di servizio:
public async Task<User> Authenticate(string username, string password) {
CurrentUser = await this._context.GetUserAsync(username.ToLower().Trim(), password.EncryptPassword());
return CurrentUser;
}
e alla fine è il codice EF nel contesto:
public async Task<User> GetUserAsync(string username, string password) {
return await this.People.AsNoTracking().OfType<User>().FirstOrDefaultAsync(u => u.Username == username && u.Password == password);
}
Aggiornamento: dopo alcune tracce la causa del congelamento dell'interfaccia utente si è rivelata essere il processo di inizializzazione. Blocchi di thread dell'interfaccia utente fino a quando il contesto EF non viene inizializzato e, una volta eseguito, il processo di query / salvataggio effettivo viene eseguito in modo asincrono.
Aggiorna l' output di debug dopo la chiamata su Task.Yield () all'inizio del gestore di clic:
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).
I metodi contrassegnati come "asincroni" sono ancora sincronizzati fino al momento in cui si verifica la prima "attesa". Per questo motivo, se uno qualsiasi di ciò che accade in quel codice iniziale richiede troppo tempo (200 ms o più penso che sia la linea guida per WinRT, che sembra ragionevole), allora si potrebbe voler forzare il codice a tornare più velocemente inserendo l'attesa in precedenza.
Ad esempio, nel tuo LoginButton_Click, puoi inserire una prima riga di " attendi Task.Yield ()" che consentirà alla chiamata di tornare più velocemente al thread dell'interfaccia utente.
Ora, solo con questa modifica, i metodi continueranno a essere eseguiti sul thread dell'interfaccia utente a causa del comportamento asincrono / atteso. Mi piace ancora fare quel cambiamento prima perché in molti casi è ciò che l'utente si aspetta che stia accadendo (il modificatore 'async' è un po 'confuso a riguardo), ed è qualcosa che puoi fare all'inizio di un handler senza dover fare casino con cose più in basso.
Il prossimo passo che possiamo fare se quanto sopra è insufficiente (come l'inizializzazione del contesto sta richiedendo troppo tempo, accade ancora sul thread dell'interfaccia utente, e blocca ancora l'interfaccia utente, solo in un momento leggermente diverso) sta prendendo quelle parti che don ' Devo succedere sul thread dell'interfaccia utente e attendere che sappiate che possono essere elaborati su qualsiasi thread, non solo sul thread dell'interfaccia utente. Questa è generalmente una buona pratica comunque per la reattività, anche in scenari in cui il codice attualmente funziona 'abbastanza veloce' per non essere un problema evidente.
Per questo, usiamo aggiungere ConfigureAwait (false) all'attività.
Una volta che entrambe le modifiche sono state apportate, entrambi 1) restituiranno il controllo al chiamante il più velocemente possibile (eseguendo la minima quantità di codice sincrono per il gestore eventi) e 2) eseguendo un lavoro che non necessita essere sul thread UI su altri thread, il che dovrebbe significare che la tua interfaccia utente non si "congela" più.
Se si blocca ancora dopo tali modifiche, potrebbe essere solo necessario eseguirlo in un debugger e, quando si blocca, interrompere per vedere quale stack del thread dell'interfaccia utente è quello di trovare il codice offendente. :)
In bocca al lupo!