it-swarm.com.de

Verwenden Sie Task.Run bei korrekter Ausführung und warten Sie nur asynchron

Ich möchte Sie nach Ihrer Meinung zu der richtigen Architektur fragen, wenn Sie Task.Run verwenden. In unserer WPF .NET 4.5-Anwendung (mit dem 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 awaitasync nicht unbedingt auf einem Hintergrund-Thread ausgeführt wird. Um die Arbeit im Hintergrund zu starten, müssen Sie den Thread mit wait Task.Run(async () => ... ) abschließen. . Durch die Verwendung von asyncawait wird die Benutzeroberfläche nicht blockiert, sie wird jedoch auf dem Benutzeroberflächenthread ausgeführt, sodass sie verzögert wird.

Wo ist der beste Ort, um Task.Run zu platzieren?

Soll ich einfach

  1. Schließen Sie den äußeren Aufruf ab, da dies für .NET weniger Threading-Arbeit bedeutet

  2. , oder sollte ich nur CPU-gebundene Methoden einbinden, die intern mit Task.Run ausgeführt werden, da dies die Wiederverwendung für andere Bereiche ermöglicht? Ich bin hier nicht sicher, ob es eine gute Idee ist, mit der Arbeit an Hintergrund-Threads tief im Kern zu beginnen.

Zu (1) wäre die erste Lösung 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.

Zu (2) wäre die zweite Lösung 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
}
278
Lukas K

Beachten Sie die Richtlinien für die Ausführung von Arbeiten an einem UI-Thread , die in meinem Blog gesammelt wurden:

  • Blockieren Sie den UI-Thread nicht länger als 50 ms auf einmal.
  • Sie können bis zu 100 Fortsetzungen im UI-Thread pro Sekunde planen. 1000 ist zu viel.

Es gibt zwei Techniken, die Sie anwenden sollten:

1) Verwenden Sie ConfigureAwait(false), wenn Sie können.

Zum Beispiel await MyAsync().ConfigureAwait(false); anstelle von await MyAsync();.

ConfigureAwait(false) teilt dem await mit, dass Sie im aktuellen Kontext nicht fortfahren müssen (in diesem Fall bedeutet "im aktuellen Kontext" "im UI-Thread"). Für den Rest dieser async -Methode (nach ConfigureAwait) können Sie jedoch nichts tun, was davon ausgeht, dass Sie sich im aktuellen Kontext befinden (z. B. Aktualisieren von UI-Elementen).

Weitere Informationen finden Sie in meinem MSDN-Artikel Best Practices in Asynchronous Programming .

2) Verwenden Sie Task.Run, um CPU-gebundene Methoden aufzurufen.

Sie sollten Task.Run verwenden, jedoch nicht in einem Code, den Sie wiederverwenden möchten (d. H. Bibliothekscode). Sie verwenden also Task.Run, um die Methode aufzurufen , nicht als Teil der Implementierung der Methode.

So würde reine CPU-gebundene Arbeit aussehen:

// Documentation: This method is CPU-bound.
void DoWork();

Was Sie mit Task.Run aufrufen würden:

await Task.Run(() => DoWork());

Methoden, die eine Mischung aus CPU-gebundenen und E/A-gebundenen Methoden darstellen, sollten eine Signatur Async mit einer Dokumentation haben, die auf ihre CPU-gebundene Natur hinweist:

// Documentation: This method is CPU-bound.
Task DoWorkAsync();

Was Sie auch mit Task.Run aufrufen würden (da es teilweise CPU-gebunden ist):

await Task.Run(() => DoWorkAsync());
328
Stephen Cleary

Ein Problem mit Ihrem ContentLoader ist, dass er intern nacheinander ausgeführt wird. Ein besseres Muster besteht darin, die Arbeit zu parallelisieren und dann am Ende zu synchronisieren, damit wir erhalten

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()
    {
        var tasks = new List<Task>();
        tasks.Add(DoCpuBoundWorkAsync());
        tasks.Add(DoIoBoundWorkAsync());
        tasks.Add(DoCpuBoundWorkAsync());
        tasks.Add(DoSomeOtherWorkAsync());

        await Task.WhenAll(tasks).ConfigureAwait(false);
    }
}

Dies funktioniert natürlich nicht, wenn für eine der Aufgaben Daten von anderen früheren Aufgaben benötigt werden, sollte aber für die meisten Szenarien einen besseren Gesamtdurchsatz liefern.

11
Paul Hatcher