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
-
Zawiń zewnętrzne wywołanie, ponieważ jest to mniej pracy z wątkami dla. NET
, 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
}
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());
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.
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