2016-03-27 9 views
1

Ich habe langsam versucht, meinen Code von der Verwendung von Delegaten zu den neuen Aufgaben in meiner WPF-Anwendung zu konvertieren. Ich mag die Tatsache, dass eine abwartende Operation mit der gleichen Methode ausgeführt werden kann, was die Anzahl der benötigten Methoden stark reduziert, die Lesbarkeit verbessert und die Wartung reduziert. Davon abgesehen habe ich beim Aufruf von EF6 Async-Methoden ein Problem mit meinem Code. Sie scheinen alle synchron zu laufen und blockieren meinen UI-Thread. Ich verwende die folgenden Technologien/Frameworks in meinem Code:Entity Framework Async Anruf blockieren UI-Thread

Als Beispiel habe ich ein LogInViewModel mit einem Befehl, der ausgeführt wird, nachdem auf meine WPF-Anwendung geklickt wurde. Hier ist der Befehl, wie im Konstruktor initialisiert:

LogInCommand = new RelayCommand(() => ExecuteLogInCommand()); 

Hier wird der Befehl Körper:

private async void ExecuteLogInCommand() 
{ 
    // Some code to validate user input 

    var user = await _userService.LogIn(username, password); 

    // Some code to confirm log in 
} 

Der Benutzer-Dienst verwendet ein generisches Repository-Objekt, das erstellt wird SimpleIoC Container MVVM Light verwenden. Die LogIn Methode sieht wie folgt aus:

public async Task<User> LogIn(string username, string password) 
{ 
    User user = await _repository.FindUser(username); 

    if (user != null && user.IsActive) 
    { 
     // Some code to verify passwords 
     return user; 
    } 

    return null; 
} 

Und mein Repository Code anzumelden:

public static async Task<User> FindUser(this IRepositoryAsync<User> repository, string username) 
{ 
    return await repository.Queryable().Where(u => u.Username == username).SingleOrDefaultAsync(); 
} 

Die SingleOrDefaultAsync() -Aufruf Entity Framework async Anruf ist. Dieser Code blockiert meinen UI-Thread. Ich habe mehrere Artikel von Stephen Cleary und anderen über Async erwarten und ordnungsgemäße Verwendung gelesen. Ich habe versucht, mit ConfigureAwait (falsch) den ganzen Weg nach unten, ohne Glück. Ich habe meinen RelayCommand-Aufruf die Async-erwarten-Schlüsselwörter ohne Glück verwendet. Ich habe den Code analysiert und die Zeile, die am längsten dauert, ist die SingleOrDefaultAsync() Zeile. Alles andere passiert fast augenblicklich. Ich habe das gleiche Problem, wenn ich andere Async-Aufrufe an die DB in meinem Code mache. Das einzige, was es sofort fixiert ist folgendes:

User user = await Task.Run(() => 
{ 
    return _userService.LogIn(Username, p.Password); 
}); 

Aber ich verstehe das nicht nötig sein sollte, da der Anruf ich auf die Datenbank zu machen bin, ist IO gebunden und nicht die CPU gebunden. Also, was ist falsch an meiner Anwendung und warum blockiert sie meinen UI-Thread?

+0

Was genau ist '.Queryable()' auf der Repository-Klasse? Auf was nennst du es? Erbt 'IRepositoryAsync 'von anderen Schnittstellen und was genau ist seine Implementierung? – Tseng

+0

@Tseng Queriable() gibt nur das zugrunde liegende Benutzer-dbset zurück, auf das Sie einen beliebigen Entity Framework-Methodenaufruf anwenden können. IRepositoryAsync ist die Schnittstelle, die vom Generic Repository/UnitOfWork Framework bereitgestellt wird. Es bietet auch eine EF6-Implementierung. Ich habe keinen Code darin hinzugefügt, weil es umfangreich ist. Ich habe oben den Link bereitgestellt, damit Sie es sich ansehen können, wenn Sie möchten. Vielen Dank! –

+0

Ihr Muster sieht gut aus, einige Empfehlungen: Verkürzen Sie die Syntax bei der Generierung zu 'new RelayCommand'; Sie brauchen nicht den '() =>' Teil. Sie können auch das 'async'- und das' await'-Schlüsselwort in Ihrem Repository-Code entfernen, wenn Sie das zurückgegebene Objekt nicht ändern. Mit dieser kleinen Änderung speichern Sie die Generierung eines in diesem Fall unnötigen Task-Objekts. Für Ihr 'async' Problem: Sie könnten versuchen, die 'Select'-Methode im' IRepositoryAsync <> 'anstelle von' Queryable() 'zu verwenden. –

Antwort

0

Ihr RelayCommand ist nicht asynchron.

LogInCommand = new RelayCommand(() => ExecuteLogInCommand()); 

Da es keine async/await Ihre ExecuteLogInCommand wird synchron aufgerufen werden.

Du hast es zu

LogInCommand = new RelayCommand(async() => await ExecuteLogInCommand()); 

zu ändern, so dass die RelayCommand async auch genannt wird.

+0

Das würde nur eine weitere Zustandsmaschine ohne praktische Wirkung erzeugen. –

+0

@PauloMorgado: Aber wenn 'ExecuteLogInCommand' aufgerufen wird ohne 'warten' oder angekettet mit' .ContinueWith() 'wird es automatisch synchron laufen, egal ob die untenstehenden Aufrufe asynchron sind oder nicht. Wenn Sie async/await verwenden, müssen Sie Async ganz nach unten verwenden. – Tseng

+0

@Tseng Ich habe versucht, den Befehl async mit den async/await-Schlüsselwörtern aufzurufen. Es blockiert immer noch. –

0

Ihr LogIn und FindUser, die nicht funktionieren sollte mit der Benutzeroberfläche sollte ConfigureAwait(false) auf alle erwartet verwenden (die according to the guidelines sollte LogInAsync und FindUserAsync genannt werden).

Alle Aufrufe sind jedoch synchron bis etwas wirklich asynchron aufgerufen wird. Ich nehme an, das wäre SingleOrDefaultAsync.

Wenn das Einbetten in Task.Run einen solchen Unterschied macht, dann muss aus irgendeinem Grund SingleOrDefaultAsync synchron ausgeführt werden.

+0

Paulo Morgado danke für Ihre Antwort. Ich habe versucht, ConfigureAwait (false) den ganzen Weg hinunter, ohne Glück. Was meinst du mit "die nicht mit der Benutzeroberfläche arbeiten sollen"? Vielen Dank! –

+0

Jede Methode, die nicht mit der Benutzeroberfläche arbeiten soll (unabhängig vom Synchronisationskontext), sollte "ConfigureAwait (false)" auf jedem "Warten" verwenden. –

Verwandte Themen