对C#的异步/等待控制流感到困惑 [英] Confused at control flow of async/await of c#

查看:53
本文介绍了对C#的异步/等待控制流感到困惑的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在学习异步/等待,并且对 await运算符将暂停执行,直到完成GetByteArrayAsync方法的工作为止.与此同时,控制权将返回给GetPageSizeAsync的调用者 "

I'm learning about async/await ,and get confused on the explaination of the await of MSDN:"The await operator suspends execution until the work of the GetByteArrayAsync method is complete. In the meantime, control is returned to the caller of GetPageSizeAsync"

我不明白的是,归还"是什么意思?首先,我相信当一个线程(例如,UI线程)到达"await"关键字时,系统将创建一个新线程(或从threadPool获取一个线程)来执行其余代码,并且UI线程可以返回到调用方方法并执行其余的操作.

What i don't understand is,what does it mean "returned"? At first,i believed when a thread(let's say, the UI thread) reaches the "await" keyword, system will create a new thread(or get a thread from threadPool) to execute the rest of codes,and UI thread can return to the caller method and execute the rest.

但是现在我知道等待"将永远不会创建线程.

But now i know that "await" will never create thread.

我编写了一个演示:

class Program
{
    static void Main(string[] args)
    {
        new Test().M1();
        Console.WriteLine("STEP:8");
        Console.Read();
    }
}

class Test
{
    public async void M1()
    {
        Console.WriteLine("STEP:1");
        var t = M2();
        Console.WriteLine("STEP:3");
        await t;
    }
    public async Task<string> M2()
    {
        Console.WriteLine("STEP:2");
        string rs = await M3();//when the thread reaches here,why don't it return to M1 and execute the STEP:3 ??
        Console.WriteLine("STEP:7");
        return rs;

    }

    public async Task<string> M3()
    {
        Console.WriteLine("STEP:4");
        var rs = Task.Run<string>(() => {
            Thread.Sleep(3000);//simulate some work that takes 3 seconds
            Console.WriteLine("STEP:6");
            return "foo";
        });
        Console.WriteLine("STEP:5");
        return await rs;
    }
}

此演示打印一些表示执行流程的标签,我认为应该是

this demo prints some labels representing the execute flow,which i thought it would be

STEP:1

STEP:2

STEP:3

STEP:4

STEP:5

STEP:6

STEP:7

STEP:8

(按顺序从1到8)

,但实际结果是: 图1

STEP:1

STEP:2

STEP:4

STEP:5

STEP:3

STEP:8

STEP:6

STEP:7

如果像MSDN的解释一样,控制权返回到M1并打印"STEP 3",那绝对是我错的,那到底是怎么回事?

If like MSDN's explaination, control is returned to M1 and print the "STEP 3", absolutely i'm wrong,So what's going on exactly?

预先感谢

推荐答案

仅仅为了摆脱它,我们无法保证在任何特定的await点我们都会做任何事情 超出了其余代码的范围.但是,在MSDN中的示例和您自己的示例中,我们将始终在每个await点处等待.

Just to get it out of the way, there's no guarantee that, at any particular await point we're going to do anything beyond carrying on with the rest of the code. However, in the sample in MSDN and your own example, we will always wait at each of the await points.

因此,我们到达了拥有await z的位置,因此我们决定要等待.这意味着a)z尚未完成(z的完成意味着什么,这是我们目前不在乎的事情),以及b)我们没有自己要做的有用的工作,此刻.

So, we reach some point where we have await z and we've decided that we're going to wait. This means a) That z hasn't Completed yet (whatever it means for z to be complete isn't something we care about at this moment in time) and b) That we have no useful work to do ourselves, at the moment.

希望从上面可以看出,为什么没有必要创建新线程"或诸如此类的事情,因为就像我刚才所说的那样,没有要做的有用工作.

Hopefully, from the above, you can see why "creating a new thread" or anything like it isn't necessary, because like I just said, there's no useful work to do.

但是,在从方法返回控制权之前,我们要加入 continuation . async机制能够表示" 完成后,安排 this 方法的其余部分从我们的await点继续".

Before we return control from our method though, we're going to enqueue a continuation. The async machinery is able to express "when z has completed, arrange for the rest of this method to continue from our await point".

在这里,诸如同步上下文和ConfigureAwait之类的东西变得很重要.我们正在运行的线程(以及我们将要放弃控制的线程)在某种程度上可能是特殊的".它可能是UI线程.它目前可能具有对某些资源的独占访问权限(请考虑ASP.Net核心前请求/响应/会话对象).

Here, things like synchronization contexts and ConfigureAwait become relevant. The thread that we're running on (and that we're about to relinquish control of) may be "special" in some way. It may be the UI thread. It may currently have exclusive access to some resources (think ASP.Net pre-Core Request/Response/Session objects).

如果是这样,希望提供特殊功能的系统安装了一个同步上下文,并且通过它我们还能够获得"何时,当我们继续执行此方法时,我们需要具有与以前相同的特殊情况".所以我们可以继续在UI线程上继续运行方法 .

If that's so, hopefully the system providing the special-ness has installed a synchronization context, and it's via that that we also are able to obtain "when we resume execution of this method, we need to have the same special circumstances we previously had". So e.g. we're able to resume running our method back on the UI thread.

默认情况下,没有同步上下文,将发现一个线程池线程来运行我们的延续.

By default, without a synchronization context, a thread pool thread will be found to run our continuation.

请注意,如果一个方法包含多个需要等待的await,则在第一次等待之后,我们将以链接的延续形式运行.我们的原始调用方在我们await第一次 时就获得了上下文.

Note that, if a method contains multiple awaits that require waiting, after that first wait, we'll be running as a chained continuation. Our original caller got their context back the first time we awaited.

在您的示例中,我们有3个await点,而所有3个 将等待并导致我们将控制权交还给调用方(并且我们没有多个await用一种方法担心之一).最后,所有这些等待都在等待Thread.Sleep完成(现代async代码中的格式较差,应改用TaskDelay).因此,在该方法中await 之后出现的任何Console.WriteLine都将被延迟.

In your sample, we have three await points and all three of them will wait and cause us to relinquish control back to our caller (and we don't have multiple awaits in a single method to worry about either). All of these awaits are, at the end of the day, waiting for that Thread.Sleep to finish (poor form in modern async code which should use TaskDelay instead). So any Console.WriteLines appearing after an await in that method will be delayed.

但是,M1async void,最好在事件处理程序之外避免. async void方法有问题,因为它们无法确定何时完成.

However, M1 is async void, something best avoided outside of event handlers. async void methods are problematic because they offer no means of determining when they've completed.

Main调用M1,该M1打印1,然后调用M2. M2打印2,然后调用M3. M3打印4,创建新的Task,打印5,然后 it 才能放弃控制.此时,它会创建一个TaskCompletionSource来表示其最终完成,从中获取Task并将其返回.

Main calls M1 which prints 1 and then calls M2. M2 prints 2 and then calls M3. M3 prints 4, creates a new Task, prints 5 and only then can it relinquish control. It's at this point that it creates a TaskCompletionSource that will represent its eventual completion, obtain the Task from it, and return that.

请注意,只有当调用M3 返回时,我们才会在M2中按下await.我们还必须在这里等待,以便我们几乎执行与M3相同的操作并返回Task.

Notice that it's only when the call to M3 returns that we hit the await in M2. We also have to wait here so we do almost the same as M3 and return a Task.

现在M1终于有了一个Task,可以打开await.但在此之前,它会打印3.它将控制权返回到Main,现在将显示8.

Now M1 finally has a Task that it can await on. But before it does that, it prints 3. It returns control to Main which now prints 8.

之后,M3rs中的Thread.Sleep完成运行,并且我们打印6. M3将其返回的Task标记为已完成,没有更多工作要做,因此退出时未安排任何继续.

Eons later, the Thread.Sleep in rs in M3 finishes running, and we print 6. M3 marks its returned Task as complete, has no more work to do, and so exits with no further continuations arranged.

M2现在可以恢复其await并打印6.完成此操作后,其Task已完成,并且M1可以最终恢复并打印7.

M2 can now resume and its await and print 6. Having done that, its Task is complete and M1 can finally resume and print 7.

M1没有Task标记为已完成,因为它是async void.

M1 doesn't have a Task to mark as complete because it was async void.

这篇关于对C#的异步/等待控制流感到困惑的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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