Причина, по которой вы видите «Готово» после «Готово!» из-за общей путаницы с асинхронными методами и не имеет ничего общего с SynchronizationContexts. SynchronizationContext контролирует, какие потоки выполняются, но у async есть свои очень специфические правила упорядочения. Иначе программы сойдут с ума! :)
'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" до "K" будут распечатаны в порядке. Вот что происходит:
«А»: прежде чем что-либо еще будет вызвано
«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 (), которая замечает, что она не завершена, и начинается ожидание ().
Тем временем ...
В этом параллельном потоке задание TPL старого стиля, созданное в DoSomething (), в конечном итоге завершает спящий режим. Он печатает "G".
Как только эта задача будет помечена как завершенная, остальная часть InnerAsync () будет запланирована на TPL для повторного выполнения, и она выведет «H». Это завершает задачу, первоначально возвращенную InnerAsync ().
Как только эта задача будет помечена как завершенная, остальная часть MiddleAsync () будет запланирована на TPL для повторного выполнения и выведет «I». Это завершает задачу, первоначально возвращенную MiddleAsync ().
Как только эта задача будет помечена как завершенная, остальная часть OuterAsync () будет запланирована на TPL для повторного выполнения, и она напечатает «J». Это завершает задачу, первоначально возвращенную OuterAsync ().
Поскольку задача OuterAsync () теперь выполнена, вызов Wait () возвращается, и Main () выводит «K».
Таким образом, даже с небольшим параллелизмом в порядке, асинхронность C # 5 по-прежнему гарантирует, что запись консоли происходит именно в этом порядке.
Дайте мне знать, если это все еще кажется запутанным:)