2016-02-22 12 views
5

Generalnie, przy wdrażaniu metody asynchronicznego na klasy, piszę coś takiego:Używa Task.Run złe praktyki?

public Task<Guid> GetMyObjectIdAsync(string objectName) 
{ 
    return Task.Run(() => GetMyObjectId(objectName)); 
} 

private Guid GetMyObjectId(string objectName) 
{ 
    using (var unitOfWork = _myUnitOfWorkFactory.CreateUnitOfWork()) 
    { 
     var myObject = unitOfWork.MyObjects.Single(o => o.Name == objectName); 
     return myObject.Id; 
    } 
} 

Ten rodzaj wzorca pozwala mi korzystać z tej samej logiki synchronicznie i asynchronicznie, w zależności od sytuacji (większość moich prac znajduje się w starej bazie kodu, a nie wiele obsługuje połączeń asynchronicznych), ponieważ mogłem publicznie udostępnić metodę synchroniczną i uzyskać maksymalną kompatybilność, jeśli potrzebuję.

Niedawno przeczytałem kilka wpisów na SO, które sugerują, że używanie Task.Run() jest złym pomysłem i powinno być używane tylko w pewnych okolicznościach, ale te okoliczności nie wydawały się zbyt jasne.

Czy wzór przedstawiony powyżej jest złym pomysłem? Czy tracę część funkcji/zamierzonego celu połączeń asynchronicznych, robiąc to w ten sposób? Czy to jest legalna implementacja?

+1

Dlaczego nie połączyć tych tajemniczych wpisów SO? Chodzi mi o to, że powinni już zawierać twoją odpowiedź, prawda? – nvoigt

+0

Nie, w zasadzie, oni zawsze mówią: "Nie powinieneś używać Task.Run()" jako swego rodzaju wyrzucanego komentarza ... ale widziałem to wystarczająco, aby mnie martwić, że mogę zrobić to źle. –

+1

https://channel9.msdn.com/Series/Three-Essential-Tips-for-Async/Async-Library-Methods-Shouldn-t-Lie – SLaks

Odpowiedz

10

To, co robisz, polega na odciążeniu synchronicznej operacji na inny wątek. Jeśli twój wątek jest "wyjątkowy", to jest w porządku. Jednym z przykładów "specjalnego" wątku jest wątek interfejsu użytkownika. W takim przypadku możesz chcieć odciążyć pracę, aby zachować responsywność interfejsu użytkownika (innym przykładem może być jakiś słuchacz).

W większości przypadków jednak przenosisz pracę z jednego wątku na drugi. To nie dodaje żadnej wartości i dodaje niepotrzebne obciążenie.

Więc:

Czy wzór Mam przedstawiony powyżej faktycznie zły pomysł?

Tak, jest. To zły pomysł, aby odciążyć pracę synchroniczną do ThreadPool i udawać, że jest asynchroniczny.

Czy tracę niektóre funkcje/przeznaczenie asynchronicznych połączeń, robiąc to w ten sposób?

Nie ma właściwie nic asynchronicznego w tej operacji. Jeśli wykonanie tego na zdalnym komputerze i można korzystać ze robi to asynchronicznie sama operacja musi być naprawdę asynchroniczny, co oznacza:

var myObject = await unitOfWork.MyObjects.SingleAsync(o => o.Name == objectName); 

Co aktualnie robi nazywa „asynchroniczny nad synchronizacją” i prawdopodobnie nie powinien tego robić. Więcej w Should I expose asynchronous wrappers for synchronous methods?

+0

Czym więc jest różnica "kod asynchroniczny" i "kod synchroniczny uruchamiany w wątku"? Czy to nie żółwie aż w dół? – CodeCaster

+0

Więc jeśli dobrze rozumiem, to nie pozwala na to, aby działał asynchronicznie, tylko pozwalając mu dopasować się do struktury asynchronicznej? Czy to jest dokładne stwierdzenie? –

+0

@ JeremyHolovacs tworzysz 'zadanie'. Ramy, z których korzystasz, to TPL (Task Parallel Library). Pojawił się przed oczekiwaniami i "żyje poniżej". Jesteście w sąsiedztwie asynchronicznym, ale tutaj nie ma nic naprawdę asynchronicznego. – i3arnon

3
public Task<Guid> GetMyObjectIdAsync(string objectName) 

Gdy widzę to, spodziewam się tam, aby być pewną przewagę w raczej niż przy użyciu tej metody tylko owijając go w Task.Run() siebie.

W szczególności spodziewam się, że uwalnia wątek, gdy trafi on w niektóre wejścia/wyjścia lub w inny sposób będzie mógł to zrobić.

teraz rozważyć, czy mam kod:

_resource = GetResourceForID(GetMyObjectIdAsync(SomeLongRunningWayToGetName())); 

Jeśli mam powodu, by trzeba mieć to zrobić w zadaniu, a ja jestem w rodzaju sytuacji Task.Run() ma właściwie sensu (mam powody, by odciążyć go na innym wątku), że najlepszym sposobem, aby to zrobić byłoby zawinąć całość:

Task task = Task.Run(() => _resource = GetResourceForID(GetMyObjectIdAsync(SomeLongRunningWayToGetName()))); 

Tutaj Task.Run()może być zły pomysł dla mnie jak dzwoniący, lub może to być dobre, ponieważ naprawdę zyskuję na tym, co mi daje.

Jednak jeśli widzę twój podpis, zamierzam myśleć, że najlepszym sposobem, aby to zrobić z kodem byłoby, aby włączyć go do kodu, który używa tej metody.

Task task = SomeLongRunningWayToGetName() 
    .ContinueWith(t => GetMyObjectIdAsync(t.Result)) 
    .ContinueWith(t => _resource = GetResourceForIDAsync(t.Result)); 

(lub podobnego używając async i await).

W najlepszym razie ma to mniejszą skuteczność w hakowaniu Task.Run(). W gorszym jestem await w to tylko po to, aby skorzystać z lepszej asynchroniczności, której nie oferuje w kontekście, który mógłby z niej skorzystać, gdyby rzeczywiście istniał. (E.g Mógłbym użyć tego w akcji MVC, którą zrobiłem asynchronicznie, ponieważ myślałem, że dodatkowy narzut zostanie spłacony w lepszym wykorzystaniu puli wątków).

Podczas gdy Task.Run() jest czasami przydatna, w tym przypadku zawsze jest źle. Jeśli nie możesz zaoferować mi większej asynchroniczności, niż sam mogę wykorzystać w klasie, nie każ mi wierzyć, że to robisz.

Oferuję tylko publiczną metodę XXXAsync(), jeśli rzeczywiście wywołuje ona asynchroniczne operacje we/wy.

Jeśli chcesz naprawdę potrzebujesz, aby wyłączyć metodę asynchroniczną, na przykład pasuje do podpisu ze wspólnej bazy lub interfejsu, to byłoby lepiej jak:

public Task<Guid> GetMyObjectIdAsync(string objectName) 
{ 
    return Task.FromResult(GetMyObjectId(objectName); 
} 

To jest złe zbyt (rozmówca nadal być lepiej po prostu dzwoniąc GetMyObjectId() bezpośrednio), ale przynajmniej jeśli kod await s wtedy, gdy działa na tym samym wątku, nie ma narzutu na użycie kolejnego wątku do wykonania pracy, więc jeśli jest zmieszany z innymi await s, negatywny wpływ jest zredukowany. Dlatego warto, abynaprawdę musiał zwrócić Task, ale nie można dodać coś przydatnego w tym, jak go nazwać.

Ale jeśli nie musisz naprawdę musisz zaoferować to,, po prostu nie rób tego.

(Prywatna metoda wywoływania Run(), ponieważ każda strona z serwisem wywoławczym czerpie z niej korzyści, jest inna i nie tylko dodajesz wygodę, a nie wywołujesz Run() w kilku miejscach, ale powinna ona być dobrze udokumentowana jako taka).

9

Ostatnio czytałem kilka postów, że SO sugerujemy użycie Task.Run() jest to zły pomysł, i powinny być stosowane tylko w określonych warunkach, ale te okoliczności nie wydaje się bardzo jasne.

Absolutnie szkielety reguły kciuka mówię ludziom, którzy są nowe asynchrony jest:

pierwsze, zrozumieć cel. Asynchronia służy do łagodzenia poważnych nieefektywności operacji o dużym opóźnieniu.

Czy to, co robisz, ma niewielkie opóźnienie? Więc nie rób tego w żaden sposób asynchronicznie. Po prostu wykonuj pracę. To jest szybkie. Używanie narzędzia do łagodzenia opóźnień w zadaniach o małym opóźnieniu powoduje, że program jest niepotrzebnie złożony.

Czy to, co robisz, wymaga dużej zwłoki, ponieważ czeka na dysku, aby się zakręciło, lub pojawi się pakiet? Ustaw jako asynchroniczny, ale nie umieszczaj go na innym wątku. Nie zatrudniamy pracownika, który usiądzie przy skrzynce pocztowej, czekając na nadejście listów; system pocztowy działa już asynchronicznie. Nie musisz zatrudniać ludzi, aby byli bardziej asynchroniczni. Przeczytaj "nie ma wątku", jeśli nie jest to jasne.

Czy praca z dużym opóźnieniem czeka na procesorze, aby wykonać olbrzymie obliczenia? Jak obliczenie, które zajmie znacznie ponad 10 ms? Następnie odinstaluj to zadanie na wątek, aby wątek mógł zostać zaplanowany na bezczynny procesor.

+0

Z Twojego komentarza _Dokładnię to jako asynchroniczne, ale nie umieszczaj go na innym wątku_, rozumiem, że podany powyżej fragment jest dokładnie odwrotny? Czy możesz podać przykład tego, w jaki sposób uczynię go asynchronicznym, ale zachowam go w tym samym wątku? –

+0

@JeremyHolovacs: Tworzysz coś asynchronicznego, ale nie wielowątkowego, przez (1) przekazanie kontroli z powrotem do osoby dzwoniącej w miejscu, w którym rozpoczyna się operacja o dużym opóźnieniu, oraz (2) pamiętanie, co należy zrobić, gdy operacja zostanie zakończona - czyli * kontynuacja * - i (3) wykrycie, kiedy operacja kończy i planowanie kontynuacji do uruchomienia. Co do tego, jak wiesz, kiedy operacja się zakończy, cóż, to zależy od operacji, prawda? Wiesz lepiej niż ja, gdy twoja operacja się zakończy. –

+0

@JeremyHolovacs: W twoim szczególnym przypadku nie wiem, co to jest wolno, że próbujesz zrobić asynchronię. Czy jest to tworzenie jednostki pracy, pobieranie zestawu obiektów, pobieranie nazwy, filtrowanie zestawu obiektów lub tworzenie identyfikatora, który może trwać dłużej niż 30 ms? Niezależnie od tego, na czym należy skoncentrować się na tworzeniu asynchronicznych. –

Powiązane problemy