Ich möchte Sie nach Ihrer Meinung zur richtigen Architektur fragen Task.Run
. In unserer WPF .NET 4.5-Anwendung (mit Caliburn Micro Framework) tritt eine verzögerte Benutzeroberfläche auf.
Grundsätzlich mache ich (sehr vereinfachte Code-Schnipsel):
public class PageViewModel : IHandle<SomeMessage>
{
...
public async void Handle(SomeMessage message)
{
ShowLoadingAnimation();
// Makes UI very laggy, but still not dead
await this.contentLoader.LoadContentAsync();
HideLoadingAnimation();
}
}
public class ContentLoader
{
public async Task LoadContentAsync()
{
await DoCpuBoundWorkAsync();
await DoIoBoundWorkAsync();
await DoCpuBoundWorkAsync();
// I am not really sure what all I can consider as CPU bound as slowing down the UI
await DoSomeOtherWorkAsync();
}
}
Aus den Artikeln / Videos, die ich gelesen / gesehen habe, weiß ich, dass sie await
async
nicht unbedingt in einem Hintergrund-Thread ausgeführt werden. Um mit der Arbeit im Hintergrund zu beginnen, müssen Sie sie mit Warten umhüllen Task.Run(async () => ... )
. Die Verwendung async
await
blockiert die Benutzeroberfläche nicht, wird jedoch im UI-Thread ausgeführt, sodass sie verzögert wird.
Wo kann Task.Run am besten platziert werden?
Soll ich nur
Schließen Sie den äußeren Aufruf ab, da dies für .NET weniger Threading-Arbeit bedeutet
, oder sollte ich nur CPU-gebundene Methoden einbinden, die intern ausgeführt werden,
Task.Run
da dies die Wiederverwendung für andere Orte ermöglicht? Ich bin mir hier nicht sicher, ob es eine gute Idee ist, mit der Arbeit an Hintergrundthreads tief im Kern zu beginnen.
Anzeige (1), die erste Lösung wäre wie folgt:
public async void Handle(SomeMessage message)
{
ShowLoadingAnimation();
await Task.Run(async () => await this.contentLoader.LoadContentAsync());
HideLoadingAnimation();
}
// Other methods do not use Task.Run as everything regardless
// if I/O or CPU bound would now run in the background.
Anzeige (2), die zweite Lösung wäre wie folgt:
public async Task DoCpuBoundWorkAsync()
{
await Task.Run(() => {
// Do lot of work here
});
}
public async Task DoSomeOtherWorkAsync(
{
// I am not sure how to handle this methods -
// probably need to test one by one, if it is slowing down UI
}
quelle
await Task.Run(async () => await this.contentLoader.LoadContentAsync());
einfach seinawait Task.Run( () => this.contentLoader.LoadContentAsync() );
. AFAIK Sie gewinnen nichts, indem Sie eine Sekundeawait
undasync
innen hinzufügenTask.Run
. Und da Sie keine Parameter übergeben, vereinfacht sich dies etwas mehrawait Task.Run( this.contentLoader.LoadContentAsync );
.Antworten:
Beachten Sie die Richtlinien für die Arbeit an einem UI-Thread , die in meinem Blog gesammelt wurden:
Es gibt zwei Techniken, die Sie verwenden sollten:
1) Verwenden
ConfigureAwait(false)
Sie, wenn Sie können.ZB
await MyAsync().ConfigureAwait(false);
stattawait MyAsync();
.ConfigureAwait(false)
teilt mit,await
dass Sie im aktuellen Kontext nicht fortfahren müssen (in diesem Fall bedeutet "im aktuellen Kontext" "im UI-Thread"). Für den Rest dieserasync
Methode (nach demConfigureAwait
) können Sie jedoch nichts tun, was davon ausgeht, dass Sie sich im aktuellen Kontext befinden (z. B. UI-Elemente aktualisieren).Weitere Informationen finden Sie in meinem MSDN-Artikel Best Practices in Asynchronous Programming .
2)
Task.Run
Zum Aufrufen von CPU-gebundenen Methoden.Sie sollten
Task.Run
Code verwenden , jedoch nicht in einem Code, den Sie wiederverwenden möchten (dh Bibliothekscode). So verwenden Sie rufen Sie die Methode, nicht im Rahmen der Durchführung des Verfahrens.Task.Run
Eine rein CPU-gebundene Arbeit würde also so aussehen:
Was Sie anrufen würden mit
Task.Run
:Methoden, die eine Mischung aus CPU-gebunden und E / A-gebunden sind, sollten eine
Async
Signatur mit einer Dokumentation haben, die auf ihre CPU-gebundene Natur hinweist:Was Sie auch mit verwenden würden
Task.Run
(da es teilweise CPU-gebunden ist):quelle
ConfigureAwait(false)
. Wenn Sie dies zuerst tun, ist dies möglicherweiseTask.Run
völlig unnötig. Wenn Sie dies noch benötigenTask.Run
, spielt es für die Laufzeit in diesem Fall keine große Rolle, ob Sie es einmal oder mehrmals aufrufen. Tun Sie also einfach das, was für Ihren Code am natürlichsten ist.ConfigureAwait(false)
Ihre CPU-gebundene Methode verwenden, ist es immer noch der UI-Thread, der die CPU-gebundene Methode ausführt, und möglicherweise wird danach nur noch alles in einem TP-Thread ausgeführt. Oder habe ich etwas falsch verstanden?Task.Run
versteht asynchrone Signaturen und wird daher erst abgeschlossen, wenn sieDoWorkAsync
abgeschlossen sind. Das extraasync
/await
ist unnötig. Ich erkläre mehr über das "Warum" in meiner Blogserie überTask.Run
Etikette .TaskCompletionSource<T>
einer ihrer Kurzschreibweisen wie zFromAsync
. Ich habe einen Blog-Beitrag, der ausführlicher beschreibt, warum für asynchrone Methoden keine Threads erforderlich sind .Ein Problem mit Ihrem ContentLoader ist, dass er intern nacheinander ausgeführt wird. Ein besseres Muster besteht darin, die Arbeit zu parallelisieren und am Ende zu synchronisieren, damit wir erhalten
Dies funktioniert natürlich nicht, wenn für eine der Aufgaben Daten von anderen früheren Aufgaben erforderlich sind, sollte jedoch für die meisten Szenarien ein besserer Gesamtdurchsatz erzielt werden.
quelle