Kiedy prawidłowo używać zadania.Biegać i kiedy tylko asynchronizować-czekać

Chciałbym zapytać o Twoją opinię na temat poprawnej architektury kiedy używać Task.Run. Doświadczam laggy UI w naszym WPF. NET 4.5 aplikacji (z Micro frameworkiem Caliburn).

W zasadzie robię (bardzo uproszczone fragmenty kodu):

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();
    }
}

Z artykułów / filmów, które czytałem/widziałem, wiem, że await async nie musi działać na wątku w tle i aby rozpocząć pracę w tle należy go zawinąć za pomocą wait Task.Run(async () => ... ). Za pomocą async await nie blokuje interfejs, ale nadal działa na wątku interfejsu, więc sprawia, że jest laggy.

Gdzie najlepiej umieścić zadanie.Uciec?

Should I just

  1. Zawiń zewnętrzne wywołanie, ponieważ jest to mniej pracy z wątkami dla. NET

  2. , Czy powinienem zawijać tylko metody związane z CPU działające wewnętrznie z Task.Run, ponieważ to sprawia, że można go ponownie użyć w innych miejscach? Nie jestem pewien, czy rozpoczęcie pracy nad wątkami tła w głębi rdzenia jest dobre pomysł.

Ad (1), pierwsze rozwiązanie byłoby takie:

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.

Ad (2), drugie rozwiązanie byłoby takie:

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
}
Author: Peter Mortensen, 2013-08-02

2 answers

Zwróć uwagę na wytyczne dotyczące wykonywania pracy na wątku UI, zebrane na moim blogu:

  • nie blokuj wątku interfejsu użytkownika przez więcej niż 50ms na raz.
  • możesz zaplanować ~100 kontynuacji w wątku interfejsu użytkownika na sekundę; 1000 to za dużo.

Są dwie techniki, których powinieneś użyć:

1) Użyj ConfigureAwait(false), Kiedy możesz.

Np. await MyAsync().ConfigureAwait(false); zamiast await MyAsync();.

ConfigureAwait(false) mówi await, że nie trzeba wznawiać na bieżący kontekst (w tym przypadku "w bieżącym kontekście "oznacza" w wątku UI"). Jednak przez resztę metody async (po ConfigureAwait), nie możesz zrobić niczego, co zakłada, że jesteś w bieżącym kontekście (np. zaktualizuj elementy interfejsu użytkownika).

Aby uzyskać więcej informacji, zobacz mój artykuł MSDN najlepsze praktyki w programowaniu asynchronicznym.

2) Użyj Task.Run, aby wywołać metody związane z procesorem.

Powinieneś używać Task.Run, ale nie w żadnym kodzie, którym chcesz być wielokrotnego użytku (np. kod biblioteki). Więc używasz Task.Run do wywołania metody, a nie jako częściimplementacji metody.

Więc czysto związana z procesorem praca wyglądałaby tak:

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

Które wywołałbyś używając Task.Run:

await Task.Run(() => DoWork());
Metody, które są mieszaniną CPU-bound i I / O-bound powinny mieć podpis Async z dokumentacją wskazującą na ich naturę CPU-bound:
// Documentation: This method is CPU-bound.
Task DoWorkAsync();

Które można również wywołać używając Task.Run (ponieważ jest częściowo związany z procesorem):

await Task.Run(() => DoWorkAsync());
 243
Author: Stephen Cleary,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-02 11:17:56

Problem z Contentloaderem polega na tym, że wewnętrznie działa on sekwencyjnie. Lepszym wzorcem jest zrównoleglenie pracy, a następnie zsynchronizowanie na końcu, więc otrzymujemy

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);
    }
}

Oczywiście nie działa to, jeśli któreś z zadań wymaga danych z innych wcześniejszych zadań, ale powinno zapewnić lepszą ogólną przepustowość dla większości scenariuszy.

 4
Author: Paul Hatcher,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-02 11:09:42