2013-08-30 4 views
12

Действительно ли Delay(0) всегда вставляется? По моему опыту, он:Task.Yield() в сравнении с 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(); 
     } 
    } 
} 

Это консольное приложение, поэтому пул потоков используется для продолжения. Выход:

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

ответ

21

Внутри Task.Delay, это выглядит следующим образом (единственная версия параметра (int) просто вызывает ниже версии):

[__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; 
} 

Как вы, надеюсь увидеть:

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

Это означает, что он всегда возвращает завершенную задачу, и поэтому ваш код всегда будет продолжать проезжать мимо этой конкретной строки await.

8

Да, это так. Проверка ИЛ в отражательных показывает (среди других логики):

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

Так что да, он передаст вам обратно уже заполненную задачу в этом случае.

Обратите внимание, что реализация await включает проверки, гарантирующие, что уже выполненная задача не вызовет дополнительный контекстный переключатель, так что да: ваш код будет работать без паузы для дыхания.

Возвращение уже завершенной задачи - рекомендуемый трюк, когда ответ уже известен/доступен синхронно; для общих значений результатов также часто используется кеш Task.

+0

Отлично, спасибо! Выглядит как удобный способ создания не-generic [задачи в завершенном состоянии] (http://stackoverflow.com/a/18527377/1768303). Принимая ответ Дэмиена, как технически он был первым:] – Noseratio

+0

@Noseratio Я думаю, используя что-то вроде 'Задача завершена = Task.FromResult (true);' лучше, потому что она гарантированно работает. Я думаю, что 'Task.Delay (0)' не требуется возвращать завершенную «Задачу». – svick

+0

@svick, я согласен 'Task.FromResult (true)' более уместно, но мне все еще нравится 'Task.Delay (миллисекунды Delay: 0)', так как я могу легко имитировать как синхронизацию, так и продолжение async, просто изменив «миллисекундыDelay». Как вы думаете, они могут изменить это поведение? Это было бы для меня изменением, давая приведенный выше код. – Noseratio

Смежные вопросы