2013-03-19 15 views
56

Jeśli korzystasz z asynchronizacji/poczekać na niższym poziomie w swojej architekturze, czy konieczne jest "bąble" wywołań asynchronicznych/oczekujących do samego końca, czy jest to nieefektywne, ponieważ zasadniczo tworzysz nowy wątek dla każdej warstwy (asynchronicznie wywołujący funkcję asynchroniczną dla każdej warstwy, czy to naprawdę nie ma znaczenia i zależy tylko od twoich preferencji?)Architektura asynchronicznego/oczekiwanego

Używam EF 6.0-alpha3, dzięki czemu mogę mieć asynchroniczne metody w . EF

Moje repozytorium jest takie:

public class EntityRepository<E> : IRepository<E> where E : class 
{ 
    public async virtual Task Save() 
    { 
     await context.SaveChangesAsync(); 
    } 
} 

Teraz moje warstwy biznesowej jest jako takie:

public abstract class ApplicationBCBase<E> : IEntityBC<E> 
{ 
    public async virtual Task Save() 
    { 
     await repository.Save(); 
    } 
} 

A potem oczywiście moja metoda w moim UI będzie mieć ten sam wzór, gdy dzwoni.

to:

  1. konieczne
  2. negatywny na wydajność
  3. tylko kwestia preferencji

Nawet jeśli to nie jest używane w oddzielnych warstwach/Projekty te same pytania dotyczy jeśli wywołuję metody zagnieżdżone w tej samej klasie:

private async Task<string> Dosomething1() 
    { 
     //other stuff 
     ... 
     return await Dosomething2(); 
    } 
    private async Task<string> Dosomething2() 
    { 
     //other stuff 
     ... 
     return await Dosomething3(); 
    } 
    private async Task<string> Dosomething3() 
    { 
     //other stuff 
     ... 
     return await Task.Run(() => ""); 
    } 

Odpowiedz

55

Jeśli używasz asynchronicznie/czekać na niższym poziomie w twojej architekturze, czy konieczne jest "spajanie" wywołań async/await do samego końca, czy jest to nieefektywne, ponieważ zasadniczo tworzysz nowy wątek dla każdej warstwy (asynchronicznie wywołując funkcję asynchroniczną dla każdej warstwy lub czy to naprawdę nie ma znaczenia i zależy tylko od twoich preferencji?

To pytanie sugeruje kilka obszarów nieporozumień .

Po pierwsze, nie twórz nowego wątku za każdym razem, gdy wywołujesz funkcję asynchroniczną.

Po drugie, nie musisz zadeklarować metody asynchronicznej, tylko dlatego, że wywołujesz funkcję asynchroniczną. Jeśli jesteś zadowolony z zadaniem, które już są zwracane po prostu wrócić, że od sposobu, który nie mają modyfikator asynchronicznej:

public class EntityRepository<E> : IRepository<E> where E : class 
{ 
    public virtual Task Save() 
    { 
     return context.SaveChangesAsync(); 
    } 
} 

public abstract class ApplicationBCBase<E> : IEntityBC<E> 
{ 
    public virtual Task Save() 
    { 
     return repository.Save(); 
    } 
} 

Ten będzie być nieco bardziej wydajne, jak to robi” t polegają na tworzeniu automatu stanów z bardzo niewielkiego powodu - ale co ważniejsze, jest to prostsze.

dowolny sposób asynchroniczny gdzie masz pojedynczy await wyraz oczekiwaniu na Task lub Task<T>, tuż po zakończeniu metody bez dalszego przetwarzania, byłoby lepiej być napisany bez użycia asynchronicznie/czekają. Więc tak:

public async Task<string> Foo() 
{ 
    var bar = new Bar(); 
    bar.Baz(); 
    return await bar.Quux(); 
} 

jest lepiej napisany jako:

public Task<string> Foo() 
{ 
    var bar = new Bar(); 
    bar.Baz(); 
    return bar.Quux(); 
} 

(Teoretycznie istnieje bardzo niewielka różnica w zadaniach tworzone i dlatego co dzwoniący mógł dodać kontynuacje, ale w zdecydowanej większości nie zauważysz żadnej różnicy.)

+0

Należy pamiętać, że można również oznaczyć wiadomość taką, jak opisana jako "asynchroniczna", aby dodać obsługę błędów. Jeśli czekasz na zadanie, możesz po prostu łączyć wywołanie w bloku 'try/catch', zamiast dodawać kontynuacje podanego zadania. – Servy

+0

@Servy: Tak, ale w takim przypadku nie spełniałoby to opisu bez dodatkowego przetwarzania. Przydaje się, że jeden * może * to zrobić bez zmiany API, pamiętaj ... –

+0

Tak więc, próbując to zrozumieć, naprawdę potrzebuję tylko async/czekać, jeśli coś, co następuje w tej samej funkcji, wymaga powrotu asynchronicznego metoda? Jeśli to jest ogień i zapomnij lub ostatnie zdanie, to nie ma potrzeby czekania? – valdetero

27

czy jest to nieefektywne, ponieważ zasadniczo tworzysz nowy wątek dla każdej warstwy (asynchronicznie wywołując funkcję asynchroniczną dla każdej warstwy, czy tak naprawdę nie ma to znaczenia i zależy tylko od twoich preferencji?

Nie. Metody asynchroniczne niekoniecznie używają nowych wątków. W tym przypadku, ponieważ bazowe wywołanie metody asynchronicznej jest metodą IO bound, naprawdę nie powinno być żadnych nowych wątków utworzonych.

to:

1. necessary 

Konieczne jest "bańka" się nazywa asynchroniczny, jeśli chcesz zachować operacja asynchroniczna. Jest to jednak naprawdę korzystne, ponieważ pozwala w pełni wykorzystać metody asynchroniczne, w tym komponowanie ich razem na całym stosie.

2. negative on performance 

Nie. Jak już wspomniałem, nie tworzy to nowych wątków. Jest trochę narzut, ale wiele z tego można zminimalizować (patrz poniżej).

3. just a matter of preference 

Nie, jeśli chcesz zachować to asynchronicznie. Musisz to zrobić, aby rzeczy były asynchroniczne na stosie.

Teraz możesz zrobić kilka rzeczy, aby poprawić perfekcję. tutaj.Jeśli jesteś po prostu owijania metody asynchronicznego, nie trzeba korzystać z funkcji językowych - po prostu zwracają Task:

public virtual Task Save() 
{ 
    return repository.Save(); 
} 

Sposób repository.Save() już zwraca Task - nie trzeba go tylko czekają aby go zawinąć w Task. Dzięki temu metoda będzie nieco bardziej wydajna.

Można również mieć swój „niski poziom” metody asynchroniczne używać ConfigureAwait aby zapobiec ich potrzeby kontekst synchronizacji numerem:

private async Task<string> Dosomething2() 
{ 
    //other stuff 
    ... 
    return await Dosomething3().ConfigureAwait(false); 
} 

To znacznie zmniejsza obciążenie związane z każdym awaitjeśli nie musisz martwić się kontekstem wywoływania. Zazwyczaj jest to najlepsza opcja podczas pracy nad kodem "biblioteki", ponieważ "zewnętrzny" await przechwytuje kontekst interfejsu użytkownika. "Wewnętrzne" funkcjonowanie biblioteki zazwyczaj nie dba o kontekst synchronizacji, więc najlepiej go nie przechwytywać.

Wreszcie, chciałbym przestrzec przed jednym ze swoich przykładach:

private async Task<string> Dosomething3() 
{ 
    //other stuff 
    ... 
    // Potentially a bad idea! 
    return await Task.Run(() => ""); 
} 

Jeśli robisz metodę transmisji asynchronicznej, które wewnętrznie używa Task.Run do „tworzenia asynchrony” wokół czegoś, co nie znajduje się asynchroniczny, skutecznie owijacie synchroniczny kod w metodę asynchroniczną. Ten będzie używać wątku ThreadPool, ale może "ukryć" fakt, że to robi, skutecznie wprowadzając w błąd API. Często lepiej jest pozostawić połączenie z numerem Task.Run dla połączeń na najwyższym poziomie i pozwolić, aby podstawowe metody pozostały synchroniczne, chyba że naprawdę są w stanie skorzystać z asynchronicznej operacji wejścia/wyjścia lub innego sposobu wyładowania niż Task.Run. (To nie jest zawsze prawda, ale „asynchroniczny” Kod zawinięty nad kodem synchronicznym poprzez Task.Run, następnie zawraca async/czekają jest często oznaką wadliwej konstrukcji.)

+0

Funkcja 'Task.Run()' w ostatniej metodzie była po prostu spreparowanym przykładem do celów przykładowych. Mogłaby to być dowolna metoda zwracająca 'Task' lub' Task 'w tym przypadku. Chciałem tylko pokazać zagnieżdżanie metod. – valdetero

+2

@valdetero Tak, ale konkretny przykład skutecznie używa wzorca z asynchronicznym/oczekiwaniem, którego ludzie nie są świadomi. Pomyślałem, że wskażę to, chociaż wspomniałem również, że niekoniecznie jest to złe, po prostu ostrożnie. –

+0

return Task.CompletedTask; –