2015-07-23 19 views
5

Próbuję uzyskać moją głowę wokół async/czekać i myślałem, że rozumiem kilka rzeczy na temat użytkowania. Ale wciąż nie całkiem jasne, jaka byłaby faktyczna korzyść w scenariuszu, jak poniżej.Co to za różnica - uruchamianie delegata akcji "async" z Task.Run (kontra domyślny delegat akcji)?

Spójrz na użycie Task.Run. Pierwsza metoda używa normalnego delegata i używa Thread.Sleep, ale druga używa "async" delegata i Task.Delay.

Moje pytanie brzmi: jak to ma wpływ na tę metodę (lub nie)?

Sama metoda jest metodą asynchroniczną. Kod tworzy osobny wątek (za pośrednictwem Task.Run), a ten wątek nie ma nic innego do roboty poza wykonywaniem tego delegata. Tak więc, nawet jeśli przynosi oczekiwanie na Task.Delay, jaki jest pożytek z tego scenariusza, ponieważ wątek jest w każdym razie izolowanym wątkiem, który nie jest używany do niczego innego, a nawet jeśli używa tylko Thread.Sleep, wątek nadal byłby kontekstem przełącz się na inne wątki procesora.

// The task thread uses a async delegate 
public async Task<bool> RetrySendEmail(MailMessage message) 
{ 
     bool emailSent = false; 
     await (Task.Run(***async()*** => 
     { 
      for (int i = 0; i < 3; i++) 
      { 
       if (emailSent) 
         break; 
       else 
         // Wait for 5 secs before trying again 
         ***await Task.Delay(5000);*** 

       try 
       { 
         Smtphost.Send(message); 
         emailSent = true; 
         break; 
       } 
       catch (Exception e) { emailSent = false; // log; } 
      } 
      return emailSent; 
     })); 
} 

// The task thread uses a normal delegate 
public async Task<bool> RetrySendEmail(MailMessage message) 
{ 
     bool emailSent = false; 
     await (Task.Run(***()*** => 
     { 
      for (int i = 0; i < 3; i++) 
      { 
       if (emailSent) 
         break; 
       else 
         // Wait for 5 secs before trying again 
         ***Thread.Sleep(5000);*** 

       try 
       { 
         Smtphost.Send(message); 
         emailSent = true; 
         break; 
       } 
       catch (Exception e){ emailSent = false; // log; } 
      } 
       return emailSent; 
     })); 
} 

Odpowiedz

5

Moje pytanie brzmi: jak to się robi żadnej różnicy dla tej metody (lub nie robi)?

Kilka różnic

  1. stosując async delegata wewnątrz Task.Run oznacza, że ​​faktycznie uruchomić Task<Task>. To jest ukryta przed tobą fakt, że Task.Run asynchroniczny jest świadomy i rozpakowuje wewnętrzną zadanie dla ciebie coś, co Task.Factory.StartNew nie zrobił
  2. Podczas korzystania z delegata asynchronicznego z Task.Run, należy utworzyć nowy wątek, a następnie uzyskując kontrolę po trafieniu await Task.Delay. Kontynuacja zostanie uruchomiona na dowolnym wątku puli wątków. Ponadto delegat jest przekształcany przez kompilator w maszynę stanu.

    Udzielając normalnego delegata, tworzysz wątek, synchronicznie blokujesz go przez 5 sekund, a następnie kontynuujesz od miejsca, w którym skończyłeś. Bez maszyn państwowych, bez ustępstw.


Tak więc, nawet jeśli przynosi ze czekają na Task.Delay, co jest wykorzystanie w tym scenariuszu, ponieważ wątek jest tak czy inaczej jedno pojedyncze nitki nie służy do niczego innego, a nawet jeśli po prostu używa Thread.leep, wątek nadal będzie przełączać kontekst, aby uzyskać inne wątki dla procesora .

Zastosowanie async z Task.Run może być, gdy chcesz zrobić zarówno CPU i IO związany pracy, wszystko w dedykowanym wątku. Masz rację myśląc, że po tym, jak delegat asynchroniczny zostanie zwrócony, zwraca się do dowolnego wątku. Chociaż, jeśli nie używałeś metody Task.Run i metody async wykonanej z wątku, do którego dołączono niestandardowy kontekst synchronizacji (na przykład WinFormSynchronizationContext), wszelkie prace po await powróciłyby do pętli komunikatów interfejsu użytkownika, chyba że użyto ConfigureAwait(false).

Prawdę mówiąc, nie widziałem tak wielu scenariuszy, w których Task.Run i async są używane poprawnie. Ale czasami ma to sens.

3

Różnica polega na tym, że marnujesz wątek i przydzielony przedział czasu.

Po zablokowaniu wątku przez 5 sekund ten wątek nie może być używany w innych częściach systemu do wykonywania rzeczywistych zadań procesora. Tworzy także przełącznik kontekstu, ponieważ wątek nie może zrobić nic innego.

Po zwolnieniu tego wątku za pomocą Task.Delay zamiast Thread.Sleep wątek może wrócić do ThreadPool, wykonać zadanie oczekiwania i wykonać je.

Ponad wszystko, gdy uwalniasz swoje wątki, kiedy możesz sprawić, że twoja aplikacja będzie bardziej skalowalna i wydajniejsza, ponieważ potrzebuje mniej zasobów do wykonania tej samej pracy lub takiej samej ilości zasobów, aby wykonać więcej pracy.


Gdy operacja jest naprawdę asynchroniczny nie ma potrzeby korzystania Task.Run (chyba trzeba wątek tła). Można po prostu wywołanie tej metody i czekają wracającego zadanie:

public async Task<bool> RetrySendEmail(MailMessage message) 
{ 
    bool emailSent = false; 
    for (int i = 0; i < 3; i++) 
    { 
     if (emailSent) 
       break; 
     else 
       await Task.Delay(5000); 

     try 
     { 
       Smtphost.Send(message); 
       emailSent = true; 
       break; 
     } 
     catch (Exception e) { emailSent = false; // log; } 
    } 
    return emailSent; 
} 
+0

Dziękuję, to jest pomocne. Trochę poza ścieżką asynchroniczną, ale w tym przykładzie utworzyłem nowy wątek (z Task.Run, ponieważ "Smtphost.Send" to blokujące wywołanie IO), więc pomyślałem, że ma sens przeniesienie tego do osobnego wątku. myślisz, że to nie ma znaczenia? –

+0

@EverythingMatters 'Task.Run' nie tworzy wątku, używa wątku' ThreadPool'. Możesz to zrobić, jeśli chcesz odciążyć pracę do innego wątku (jest to użyteczne na przykład, aby odblokować wątek GUI), ale to nie poprawia wydajności .Jeśli oryginalny wątek jest wątkiem "ThreadPool", blokowanie innego nie dodaje żadnej wartości. – i3arnon