2015-04-07 2 views
3

Мне интересно, как поток async работает через стек. При чтении о async в C#, вы будете постоянно читать некоторые версии следующие:Асинхронное/ожидание поведения через стек

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

Это возвращение к вашему абоненту сразу часть, которая меня смущает. В приведенном ниже примере, если что-то вызывает MethodOneAsync(), исполнение попадает в ожидании в MethodOneAsync. Он сразу возвращается к методу, который называется этим? Если да, то как выполняется MethodTwoAsync? Или он продолжает работать над стеком, пока не обнаружит, что он фактически заблокирован (то есть DoDBWorkAsync()), а затем выдаст поток?

public static async Task MethodOneAsync() 
{ 
    DoSomeWork(); 
    await MethodTwoAsync(); 
} 

public static async Task MethodTwoAsync() 
{ 
    DoSomeWork(); 
    await MethodThreeAsync(); 
} 

public static async Task MethodThreeAsync() 
{ 
    DoSomeWork(); 
    await DoDBWorkAsync(); 
} 

ответ

3

Часть перед await в async метода выполняется синхронно. Это относится ко всем методам async.

Предположим, что вместо await DoDBWorkAsync() у нас есть await Task.Delay(1000).

Это означает, что MethodOneAsync запускается, выполняет DoSomeWork и вызовы MethodTwoAsync, которые, в свою очередь выполняет DoSomeWork, который вызывает MethodThreeAsync который снова выполняет DoSomeWork.

Затем он называет Task.Delay(1000), получает незавершенный Task и ждет его.

Ожидание логически эквивалентно добавлению продолжения и возврату задачи обратно вызывающему абоненту, который равен MethodTwoAsync, который делает то же самое и возвращает Task вызывающему и т. Д. И т. Д.

Таким образом, когда корневая задержка Task завершается, все продолжения могут выполняться один за другим.


Если мы делаем ваш пример немного сложнее:

public static async Task MethodOneAsync() 
{ 
    DoSomeWorkOne(); 
    await MethodTwoAsync(); 
    DoMoreWorkOne(); 
} 

public static async Task MethodTwoAsync() 
{ 
    DoSomeWorkTwo(); 
    await MethodThreeAsync(); 
    DoMoreWorkTwo(); 
} 

public static async Task MethodThreeAsync() 
{ 
    DoSomeWorkThree(); 
    await Task.Delay(1000); 
    DoMoreWorkThree(); 
} 

Было бы логично похоже делать это с продолжениями:

public static Task MethodOneAsync() 
{ 
    DoSomeWorkOne(); 
    DoSomeWorkTwo(); 
    DoSomeWorkThree(); 
    return Task.Delay(1000). 
     ContinueWith(_ => DoMoreWorkThree()). 
     ContinueWith(_ => DoMoreWorkTwo()). 
     ContinueWith(_ => DoMoreWorkOne()); 
} 
+0

Да, ваша версия намного легче читать, чем моя версия (мы в значительной степени говорили то же самое) +1 к вам. –

0

Он вернется после стрельбе из Task для MethodTwoAsync метод (или продолжение исполнения, если это Task немедленно сделано), поэтому внутренний Task работает в среде SynchronizationContext.Current.

И после этого Task, конечный компьютер .NET возвращает выполнение в точку сразу после стрельбы MethodTwoAsync и обрабатывает остальную часть кода.

Смотрите более подробную информацию на MSDN article:

enter image description here

1

Во-первых, позвольте мне сделать другой пример, чтобы мой код дальше будет иметь смысл

public static async Task MethodOneAsync() 
{ 
    DoSomeWork1(); 
    await MethodTwoAsync(); 
    DoOtherWork1(); 
} 

public static async Task MethodTwoAsync() 
{ 
    DoSomeWork2(); 
    await MethodThreeAsync(); 
    DoOtherWork2(); 
} 

public static async Task MethodThreeAsync() 
{ 
    DoSomeWork3(); 
    await DoDBWorkAsync(); 
    DoOtheWork3(); 
} 

Все асинхронной ждут делает, поверните выше код в чем-то подобном (но даже это ОГРОМНОЕ упрощение) это

public static Task MethodOneAsync() 
{ 
    DoSomeWork1(); 
    var syncContext = SynchronizationContext.Current ?? new SynchronizationContext(); 
    var resultTask = MethodTwoAsync(); 
    return resultTask.ContinueWith((task) => 
    { 
     syncContext.Post((state) => 
     { 
      SynchronizationContext.SetSynchronizationContext(syncContext); 
      DoOtherWork1(); 
     }, null); 
    }); 
} 

public static Task MethodTwoAsync() 
{ 
    DoSomeWork2(); 
    var syncContext = SynchronizationContext.Current ?? new SynchronizationContext(); 
    var resultTask = MethodThreeAsync(); 
    return resultTask.ContinueWith((task) => 
    { 
     syncContext.Post((state) => 
     { 
      SynchronizationContext.SetSynchronizationContext(syncContext); 
      DoOtherWork2(); 
     }, null); 
    }); 
} 

public static Task MethodThreeAsync() 
{ 
    DoSomeWork3(); 
    var syncContext = SynchronizationContext.Current ?? new SynchronizationContext(); 
    var resultTask = DoDbWorkAsync(); 
    return resultTask.ContinueWith((task) => 
    { 
     syncContext.Post((state) => 
     { 
      SynchronizationContext.SetSynchronizationContext(syncContext); 
      DoOtherWork3(); 
     }, null); 
    }); 
}. 

Каждое ожидание просто выполняет следующий уровень глубже, пока не будет принудительно возвращать задачу, как только это произойдет, он начнет выполнение продолжений по задачам, когда они завершатся, и в этом продолжении он переходит в делегат, представляющий «остальную функцию», до SynchronizationContext.Post(, чтобы получить запланированный код.

Как планируется, зависит оттого, что вы находитесь. В WPF и Winforms он ставит его в очередь на насос сообщений, поскольку по умолчанию и ASP.NET он ставит его в очередь на рабочем столе пула.

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