Причина, по которой вы видите "Готово" после "Готово!" происходит из-за общей путаницы с асинхронными методами и не имеет ничего общего с SynchronizationContexts. SynchronizationContext контролирует, в каком потоке выполняется что-то, но «асинхронный» имеет свои очень специфические правила упорядочения. Иначе программы сошли бы с ума! :)
'await' гарантирует, что остальная часть кода в текущем асинхронном методе не будет выполняться до тех пор, пока не завершится ожидаемая вещь. Он ничего не обещает о звонящем.
Ваш асинхронный метод возвращает 'void', который предназначен для асинхронных методов, которые не позволяют исходному вызывающему объекту полагаться на завершение метода. Если вы хотите, чтобы ваш вызывающий объект также ждал, вам нужно убедиться, что ваш асинхронный метод возвращает Task
(в случае, если вы хотите, чтобы наблюдались только завершение/исключения) или Task<T>
, если вы действительно хотите также вернуть значение. Если вы объявите возвращаемый тип метода одним из этих двух, то компилятор позаботится обо всем остальном, о создании задачи, представляющей вызов этого метода.
Например:
static void Main(string[] args)
{
Console.WriteLine("A");
// in .NET, Main() must be 'void', and the program terminates after
// Main() returns. Thus we have to do an old fashioned Wait() here.
OuterAsync().Wait();
Console.WriteLine("K");
Console.ReadKey();
}
static async Task OuterAsync()
{
Console.WriteLine("B");
await MiddleAsync();
Console.WriteLine("J");
}
static async Task MiddleAsync()
{
Console.WriteLine("C");
await InnerAsync();
Console.WriteLine("I");
}
static async Task InnerAsync()
{
Console.WriteLine("D");
await DoSomething();
Console.WriteLine("H");
}
private static Task DoSomething()
{
Console.WriteLine("E");
return Task.Run(() =>
{
Console.WriteLine("F");
for (int i = 1; i < 10; i++)
{
Thread.Sleep(100);
}
Console.WriteLine("G");
});
}
В приведенном выше коде буквы от «А» до «К» будут напечатаны по порядку. Вот что происходит:
«A»: до того, как будет вызвано что-либо еще
"B": OuterAsync() вызывается, Main() все еще ожидает.
"C": вызывается MiddleAsync(), OuterAsync() все еще ожидает завершения выполнения MiddleAsync().
"D": InnerAsync() вызывается, MiddleAsync() все еще ожидает завершения InnerAsync().
«E»: DoSomething() вызывается, InnerAsync() все еще ожидает завершения DoSomething(). Он немедленно возвращает задачу, которая запускается параллельно.
Из-за параллелизма существует гонка между InnerAsync(), завершающим проверку полноты задачи, возвращаемой DoSomething(), и фактическим запуском задачи DoSomething().
Как только DoSomething() запускается, он выводит «F», а затем засыпает на секунду.
В то же время, если планирование потоков не слишком запутано, InnerAsync() теперь почти наверняка поняла, что DoSomething() еще не завершена. Теперь начинается асинхронная магия.
InnerAsync() отключается от стека вызовов и сообщает, что его задача не завершена. Это заставляет MiddleAsync() отключиться от стека вызовов и сказать, что его собственная задача не завершена. Это приводит к тому, что OuterAsync() отключается от стека вызовов и сообщает, что его задача также не завершена.
Задача возвращается в Main(), который замечает, что она не завершена, и начинается вызов Wait().
тем временем...
В этом параллельном потоке задача TPL старого стиля, созданная в DoSomething(), в конце концов перестает спать. Он печатает «Г».
Как только эта задача помечается как выполненная, остальная часть InnerAsync() назначается TPL для повторного выполнения и выводит «H». Это завершает задачу, первоначально возвращенную InnerAsync().
Как только эта задача помечается как выполненная, остальная часть MiddleAsync() назначается TPL для повторного выполнения и выводит «I». Это завершает задачу, первоначально возвращенную MiddleAsync().
Как только эта задача помечается как выполненная, остальные функции OuterAsync() планируются в TPL для повторного выполнения и выводят "J". Это завершает задачу, первоначально возвращенную OuterAsync().
Поскольку задача OuterAsync() завершена, вызов Wait() возвращается, а Main() выводит «K».
Таким образом, даже с небольшим количеством параллелизма в порядке, асинхронный режим C# 5 по-прежнему гарантирует, что запись в консоль происходит именно в этом порядке.
Дайте мне знать, если это все еще кажется запутанным :)
person
Theo Yaung
schedule
26.10.2011