2014-11-12 10 views
14

mam jedną usługę powiedzmy,Oczekujcie lub Task.FromResult

public interface ISomeService 
{ 
    Task<bool> DoSomeExpensiveCheckAsync(string parameter); 
} 

I mam tę klasę do konsumpcji usługi. Wystarczy wykonać kilka prostych sprawdzeń zerowych, a następnie zwrócić odpowiedź usługi.

public class SomeServiceConsumer 
{ 
    private readonly ISomeService _serviceClient; 

    public SomeServiceConsumer(ISomeService serviceClient) 
    { 
     _serviceClient = serviceClient; 
    } 

    public async Task<bool> DoSomething1Async(string someParameter) 
    { 
     if (string.IsNullOrWhiteSpace(someParameter)) 
     { 
      return false; 
     } 
     return await _serviceClient.DoSomeExpensiveCheckAsync(someParameter); 
    } 

    //No async or await keywords 
    public Task<bool> DoSomething2Async(string someParameter) 
    { 
     if (string.IsNullOrWhiteSpace(someParameter)) 
     { 
      return Task.FromResult(false); 
     } 
     return _serviceClient.DoSomeExpensiveCheckAsync(someParameter); 
    } 
} 

powinienem zrobić DoSomething1Async lub DoSomething2Async?

Według this answer, nie powinien zawijać z niepotrzebnym await ale potem muszę używać Task.FromResult(false) dla shortcircuiting jak w DoSomething2Async

Ale według this answer istnieją przypadki try/catch i using sprawozdania gdzie faktycznie powinien await przed powracający.

Czy mam rację mówiąc następnie, że

  1. Jeśli muszę używać try/catch lub using potem powinno await

  2. Inaczej nie await jeśli idziesz tylko do powrotu. I używać Task.FromResult do zwarcia

Lubię DoSomething1Async więcej, i chcę to zrobić wszędzie, jeśli ktoś mówi, że nie robi sprawy :).

Odpowiedz

13

Jeśli martwisz się o to, cache Task:

static readonly Task<bool> falseTask = Task.FromResult(false); 

async kluczowe otula również wyjątki w zwracana Task, wraz z odpowiednim ślad stosu. To kompromis, bezpieczeństwo zachowania dla perf.

Spójrzmy na scenariuszach różnicy, gdzie każdy będzie inaczej:

async Task UseSomething1Async(string someParameter) 
{ 
    // if IsNullOrWhiteSpace throws an exception, it will be wrapped in 
    // the task and not thrown here. 
    Task t1 = DoSomething1Async(someParameter); 

    // rather, it'll get thrown here. this is best practice, 
    // it's what users of Task-returning methods expect. 
    await t1; 

    // if IsNullOrWhiteSpace throws an exception, it will 
    // be thrown here. users will not expect this. 
    Task t2 = DoSomething2Async(someParameter); 

    // this would never have been reached. 
    await t2; 
} 

Wystarczy ilustrujący punkt tutaj - IsNullOrWhiteSpace faktycznie nie rzucać żadnych wyjątków dla jakiegokolwiek powodu.

Jeśli chodzi o śledzenie stosu, ślady stosu asynchronicznego są określane w miejscu, w którym znajduje się await. No await oznacza, że ​​metoda zniknie ze śledzenia stosu.

Powiedz DoSomeExpensiveCheckAsync zgłasza wyjątek. W przypadku DoSomething1Async ślad stosu będzie wyglądał jak caller -> DoSomething1Async -> DoSomeExpensiveCheckAsync.

W przypadku DoSomething2Async, ślad stosu będzie wyglądać tak: caller -> DoSomeExpensiveCheckAsync. W zależności od złożoności kodu może to utrudnić debugowanie.

W praktyce generalnie zwracam tylko bezpośrednio Task, jeśli wiedziałem, że nie zostaną wyrzucone żadne wyjątki, a jeśli nazwa metody byłaby tylko przeciążeniem przekazywania do innego przeciążenia.Zawsze są wyjątki od tej reguły, muszą istnieć miejsca, w których chcesz zmaksymalizować wydajność. Po prostu wybierz i wybierz ostrożnie, zdaj sobie sprawę, że możesz utrudniać życie Tobie i Twojemu użytkownikowi.

+0

Dzięki Cory. Nie rozumiem jednak, jak wpływa to na bezpieczeństwo zachowania. Zobacz mój komentarz do odpowiedzi @ I3arnon. – labroo

+0

Edytowałem w wyjaśnieniu. –

4

To naprawdę nie ma znaczenia. Jeśli masz pewność, że zawsze oznaczasz Task - zwracając metody za pomocą słowa kluczowego async, użyj narzędzia DoSomething1.

Jak pan powiedział, jest to kompromis:

  • DoSomething2 nie generuje stan maszyny potrzebne dla metody async i dlatego nieznacznie szybciej (ale różnica jest głównie znikoma).

  • Z drugiej strony może to mieć jakieś nieprzewidziane skutki uboczne dotyczące obsługi wyjątków, ponieważ w metodzie async wyjątek będzie przechowywany w zwróconym Task aw drugiej byłoby regularnie wyrzucane.

+0

Nie rozumiem różnicy wyjątków @ I3arnon. jeśli zrobię DoSomeExpensiveCheckAsync w usłudze rzucić wyraźny wyjątek, to "var from1 = czekać na someServiceCustomer.DoSomething1Async (" Hello ");" i " var from2 = czekaj someServiceCustomer.DoSomething2Async (" Hello ");" podać dokładnie ten sam wyjątek. DoSomething1Async ma jednak dodatkowy TaskAwaiter.ThrowForNonSuccess w stosie stacktrace. To pokazuje, że DoSomething1Async robi coś ekstra, ale nie dostaję nieprzewidzianych efektów ubocznych. – labroo

+0

@labroo Aby zrozumieć różnicę, musisz pomyśleć, co by się stało, gdyby nie 'await':' var task = DoSomethingAsync ("Hello") '. Jeśli istnieje wyjątek, a metoda jest asynchroniczna, nie spowoduje to wyjątku. Wyjątek zostanie zapisany w zadaniu i ponownie wyświetlony w odpowiedzi na "oczekuj". Jeśli metoda nie jest asynchroniczna, wyjątek byłby wywoływany tak jak każda inna metoda. – i3arnon

+0

@labroo Oto kilka dodatkowych informacji: http://stackoverflow.com/a/24441859/885318 – i3arnon

Powiązane problemy