Async /等待没有按预期反应

Ale*_*idt 6 c# async-await

使用下面的代码我希望字符串"Finished"出现在控制台上的"Ready"之前.任何人都可以向我解释,为什么等待完成这个样本中的任务?

    static void Main(string[] args)
    {
        TestAsync();
        Console.WriteLine("Ready!");
        Console.ReadKey();
    }

    private async static void TestAsync()
    {
        await DoSomething();
        Console.WriteLine("Finished");
    }

    private static Task DoSomething()
    {
        var ret = Task.Run(() =>
            {
                for (int i = 1; i < 10; i++)
                {
                    Thread.Sleep(100);
                }
            });
        return ret;
    }
Run Code Online (Sandbox Code Playgroud)

The*_*ung 28

你在"准备好"之后看到"完成"的原因!是因为异步方法常见的混淆点,与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");
        });
}
Run Code Online (Sandbox Code Playgroud)

在上面的代码中,"A"到"K"将按顺序打印出来.这是发生了什么:

"A":在调用其他任何东西之前

"B":正在调用OuterAsync(),Main()仍在等待.

"C":正在调用MiddleAsync(),OuterAsync()仍在等待查看MiddleAsync()是否完整.

"D":正在调用InnerAsync(),MiddleAsync()仍在等待看看InnerAsync()是否完整.

"E":正在调用DoSomething(),InnerAsync()仍在等待DoSomething()是否完成.它会立即返回一个并行启动的任务.

由于并行性,InnerAsync()完成了对DoSomething()返回的任务的完整性测试以及实际启动的DoSomething()任务之间的竞争.

一旦DoSomething()启动,它打印出"F",然后休眠一秒钟.

同时,除非线程调度超级混乱,否则InnerAsync()几乎肯定已经意识到DoSomething()还没有完成.现在异步魔法开始了.

InnerAsync()将自己从callstack中拉出来,并说它的任务是不完整的.这会导致MiddleAsync()从callstack中退出并说它自己的任务不完整.这导致OuterAsync()将自己从callstack中拉出来,并说它的任务也是不完整的.

任务返回Main(),注意到它不完整,Wait()调用开始.

与此同时...

在该并行线程上,在DoSomething()中创建的旧式TPL任务最终完成休眠.它打印出"G".

一旦该任务被标记为完成,其余的InnerAsync()将在TPL上进行调度以再次执行,并打印出"H".这完成了InnerAsync()最初返回的任务.

一旦任务被标记为完成,其余的MiddleAsync()将在TPL上进行调度以再次执行,并打印出"I".这完成了MiddleAsync()最初返回的任务.

一旦任务被标记为完成,剩余的OuterAsync()就会在TPL上被调度以再次执行,并打印出"J".这完成了OuterAsync()最初返回的任务.

由于OuterAsync()的任务现在完成,Wait()调用返回,Main()打印出"K".

因此,即使顺序中有一点并行性,C#5异步仍然保证控制台写入按照确切的顺序发生.

让我知道这是否仍然令人困惑:)

  • 应该避免`Wait`,因为它包装了`AggregateException`中的任何异常; 这是TPL的延续.使用`SynchronizationContext`等待`TestAsync`或[使用`TestAsync().GetAttiter().GetResult()`](http://blogs.msdn.com/b/pfxteam/archive/2011/09/28 /10217876.aspx). (3认同)
  • 同意 - `.GetAwaiter().GetResult()`在大多数情况下比老式`.Wait()`更好.:)我只是想为这个例子选择任何阻塞调用,我觉得进入他们之间的微妙是一个不同的主题:) (3认同)

Nic*_*ler 16

你在一个控制台应用程序中,所以你没有专门的SynchronizationContext,这意味着你的继续将在线程池线程上运行.

此外,你是不是等待调用TestAsync()Main.这意味着当您执行此行时:

await DoSomething();
Run Code Online (Sandbox Code Playgroud)

TestAsync方法返回控制权Main,只是继续正常执行 - 即输出"就绪!" 并等待按键.

同时,第二次DoSomething完成后,awaitin TestAsync将继续在线程池线程上输出"Finished".