2013-08-30 14 views
12

Czy zawsze ma się wbudowany znak Delay(0)? Z mojego doświadczenia, to robi:Task.Yield() kontra Task.Delay (0)

using System; 
using System.Threading; 
using System.Threading.Tasks; 

namespace ConsoleApplication 
{ 
    class Program 
    { 
     static async Task Test() 
     { 
      await Task.Yield(); 
      Console.WriteLine("after Yield(), thread: {0}", Thread.CurrentThread.ManagedThreadId); 
      await Task.Delay(0); 
      Console.WriteLine("after Delay(0), thread: {0}", Thread.CurrentThread.ManagedThreadId); 
      await Task.Delay(100); 
      Console.WriteLine("after Delay(100), thread: {0}", Thread.CurrentThread.ManagedThreadId); 
     } 
     static void Main(string[] args) 
     { 
      Console.WriteLine("Main thread: {0}", Thread.CurrentThread.ManagedThreadId); 
      Test().Wait(); 
     } 
    } 
} 

Jest to aplikacja konsoli, dzięki czemu pula wątek służy do kontynuacji. Wyjście:

Main thread: 11 
after Yield(), thread: 7 
after Delay(0), thread: 7 
after Delay(100), thread: 6 

Odpowiedz

21

Wewnątrz Task.Delay, wygląda to tak (pojedynczy parametr (int) wersja po prostu wywołuje poniższego wersji):

[__DynamicallyInvokable] 
public static Task Delay(int millisecondsDelay, CancellationToken cancellationToken) 
{ 
    if (millisecondsDelay < -1) 
    { 
     throw new ArgumentOutOfRangeException("millisecondsDelay", Environment.GetResourceString("Task_Delay_InvalidMillisecondsDelay")); 
    } 
    if (cancellationToken.IsCancellationRequested) 
    { 
     return FromCancellation(cancellationToken); 
    } 
    if (millisecondsDelay == 0) 
    { 
     return CompletedTask; 
    } 
    DelayPromise state = new DelayPromise(cancellationToken); 
    if (cancellationToken.CanBeCanceled) 
    { 
     state.Registration = cancellationToken.InternalRegisterWithoutEC(delegate (object state) { 
      ((DelayPromise) state).Complete(); 
     }, state); 
    } 
    if (millisecondsDelay != -1) 
    { 
     state.Timer = new Timer(delegate (object state) { 
      ((DelayPromise) state).Complete(); 
     }, state, millisecondsDelay, -1); 
     state.Timer.KeepRootedWhileScheduled(); 
    } 
    return state; 
} 

Jak można mieć nadzieję zobaczyć:

if (millisecondsDelay == 0) 
    { 
     return CompletedTask; 
    } 

Co oznacza, że ​​zawsze zwraca ukończone zadanie, a zatem Twój kod zawsze będzie kontynuował pracę poza tą konkretną linią await.

8

Tak, robi. Czek IL w pokazach reflektor (wśród innych logiki):

if (millisecondsDelay == 0) 
{ 
    return CompletedTask; 
} 

Więc tak, że odda ci z powrotem to już wypełniony zadaniem w tej sprawie.

Należy pamiętać, że implementacja obejmuje sprawdzenia, które zapewniają, że zakończone zadanie nie powoduje dodatkowego przełączenia kontekstu, więc tak: kod będzie działał bez zatrzymywania oddechu.

Zwrócenie ukończonego zadania jest zalecanym trikiem, gdy odpowiedź jest już znana/dostępna synchronicznie; często jest też buforowana Task s dla wspólnych wartości wyników.

+0

Świetne, dzięki! Wygląda mi to na poręczny sposób tworzenia nietypowego [zadania w stanie ukończonym] (http://stackoverflow.com/a/18527377/1768303). Przyjmując odpowiedź Damiena, technicznie był pierwszy:] – Noseratio

+0

@Noseratio Myślę, że używając czegoś w stylu 'Zadanie zakończone = Zadanie.Zasilanie (prawda);' jest lepsze, ponieważ gwarantuje działanie. Myślę, że 'Task.Delay (0)' nie jest wymagane, aby zwrócić zakończone 'zadanie'. – svick

+0

@svick, Zgadzam się z 'Task.FromResult (true)' jest bardziej odpowiednie, ale nadal lubię 'Task.Delay (millisecondsDelay: 0)' ponieważ mogę łatwo symulować kontynuację synchronizacji i asynchronizacji tylko przez zmianę 'millisecondsDelay'. Czy myślisz, że mogą zmienić to zachowanie? To byłoby dla mnie przełomowe, dając powyższy kod. – Noseratio

Powiązane problemy