异步/等待未按预期反应 [英] Async/await not reacting as expected

查看:28
本文介绍了异步/等待未按预期反应的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

使用下面的代码,我希望字符串Finished"出现在控制台上的Ready"之前.谁能给我解释一下,为什么 await 不会等待完成此示例中的任务?

Using the code below I expect the string "Finished" to appear before "Ready" on the console. Could anybody explain to me, why await will not wait for finishing the task in this sample?

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

推荐答案

在Ready!"之后看到Finished"的原因是因为与异步方法的常见混淆点,与 SynchronizationContexts 无关.SynchronizationContext 控制运行在哪个线程上,但是async"有自己非常具体的排序规则.否则程序会发疯!:)

The reason why you're seeing "Finished" after "Ready!" is because of a common confusion point with async methods, and has nothing to do with SynchronizationContexts. SynchronizationContext control which thread things run on, but 'async' has its own very specific rules about ordering. Otherwise programs would go crazy! :)

'await' 确保 当前异步方法 中的其余代码在等待的事情完成之前不会执行.它不对调用者做出任何承诺.

'await' ensures that the rest of the code in the current async method doesn't execute until after the thing awaited completes. It doesn't promise anything about the caller.

您的异步方法返回void",这适用于不允许原始调用者依赖方法完成的异步方法.如果你希望你的调用者也等待,你需要确保你的异步方法返回一个 Task(如果你只想观察完成/异常),或者一个 Task; 如果您实际上还想返回一个值.如果您将方法的返回类型声明为这两者之一,那么编译器将负责其余的工作,即生成代表该方法调用的任务.

Your async method returns 'void', which is intended for async methods that don't allow for the original caller to rely on method completion. If you want your caller to also wait, you'll need to make sure your async method returns a Task (in case you only want completion/exceptions observed), or a Task<T> if you actually want to return a value as well. If you declare the return type of the method to be either of those two, then the compiler will take care of the rest, about generating a task that represents that method invocation.

例如:

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"会依次打印出来.这是发生了什么:

In the above code, "A" through "K" will print out in order. Here's what's going on:

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

"A": Before anything else gets called

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

"B": OuterAsync() is being called, Main() is still waiting.

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

"C": MiddleAsync() is being called, OuterAsync() is still waiting to see if MiddleAsync() is complete or not.

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

"D": InnerAsync() is being called, MiddleAsync() is still waiting to see if InnerAsync() is complete or not.

"E":正在调用 DoSomething(),InnerAsync() 仍在等待查看 DoSomething() 是否完成.它立即返回一个任务,该任务以并行开始.

"E": DoSomething() is being called, InnerAsync() is still waiting to see if DoSomething() is complete or not. It immediately returns a task, which starts in parallel.

由于并行性,在 InnerAsync() 完成对 DoSomething() 返回的任务的完整性测试与实际开始的 DoSomething() 任务之间存在竞争.

Because of parallelism, there is a race between InnerAsync() finishing its test for completeness on the task returned by DoSomething(), and the DoSomething() task actually starting.

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

Once DoSomething() starts, it prints out "F", then sleeps for a second.

与此同时,除非线程调度超级混乱,InnerAsync() 现在几乎可以肯定已经意识到 DoSomething() 尚未完成.现在异步魔法开始了.

In the meanwhile, unless thread scheduling is super messed up, InnerAsync() almost certainly has now realized that DoSomething() is not yet complete. Now the async magic starts.

InnerAsync() 将自己从调用堆栈中拉出,并表示其任务未完成.这会导致 MiddleAsync() 将自己从调用堆栈中拉出并说它自己的任务未完成.这会导致 OuterAsync() 将自己从调用堆栈中拉出,并说它的任务也未完成.

InnerAsync() yanks itself off the callstack, and says that its task is incomplete. This causes MiddleAsync() to yank itself off the callstack and say that its own task is incomplete. This causes OuterAsync() to yank itself off the callstack, and say that its task is incomplete as well.

任务返回给 Main(),Main() 注意到它未完成,Wait() 调用开始.

The task is returned to Main() which notices it's incomplete, and the Wait() call begins.

同时...

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

On that parallel thread, the old-style TPL Task created in DoSomething() eventually finishes sleeping. It prints out "G".

一旦该任务被标记为完成,InnerAsync() 的其余部分将被安排在 TPL 上再次执行,并打印出H".这样就完成了最初由 InnerAsync() 返回的任务.

Once that task gets marked as complete, the rest of InnerAsync() gets scheduled on the TPL to get executed again, and it prints out "H". That completes the task originally returned by InnerAsync().

一旦那个任务被标记为完成,MiddleAsync() 的其余部分就会被安排在 TPL 上再次执行,并打印出I".这样就完成了 MiddleAsync() 最初返回的任务.

Once that task gets marked complete, the rest of MiddleAsync() gets scheduled on the TPL to get executed again, and it prints out "I". That completes the task originally returned by MiddleAsync().

一旦那个任务被标记为完成,OuterAsync() 的其余部分就会被安排在 TPL 上再次执行,并打印出J".这样就完成了 OuterAsync() 最初返回的任务.

Once that task gets marked complete, the rest of OuterAsync() gets scheduled on the TPL to get executed again, and it prints out "J". That completes the task originally returned by OuterAsync().

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

Since OuterAsync()'s task is now complete, the Wait() call returns, and Main() prints out "K".

因此,即使在顺序上有一点并行,C# 5 async 仍然保证控制台写入以确切的顺序发生.

Thus even with a little bit of parallelism in the order, C# 5 async still guarantees that the console writing occurs in that exact order.

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

Let me know if this still seems confusing :)

这篇关于异步/等待未按预期反应的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆