C# “等待”任务在哪里执行?

C# “等待”任务在哪里执行?,c#,multithreading,asynchronous,task-parallel-library,async-await,C#,Multithreading,Asynchronous,Task Parallel Library,Async Await,考虑以下几点: private async void btnSlowPoke_Click(object sender, EventArgs e) { await DoItAsync(); } private async Task<int> SomeLongJobAsync() { for (int x = 0; x < 999999; x++) { //ponder my existence for one second

考虑以下几点:

private async void btnSlowPoke_Click(object sender, EventArgs e)
{
    await DoItAsync();
}

private async Task<int> SomeLongJobAsync()
{
    for (int x = 0; x < 999999; x++)
    {
        //ponder my existence for one second
        await Task.Delay(1000);
    }
    return 42;
}

public async Task<int> DoItAsync()
{
    Console.Write("She'll be coming round the mountain");
    Task<int> t = SomeLongJobAsync();  //<--On what thread does this execute?
    Console.WriteLine(" when she comes.");
    return await t;
}
执行DoItAsync中的第一次写入。 SomeLongJobAsync启动。 DoItAsync中的WriteLine执行。 DoItAsync暂停,而SomeLongJobAsync一直工作到完成为止。 SomeLongJobAsync完成,因此DoItAsync返回。 同时,UI具有响应性

SomeLongJobAsync在哪个线程上执行?

简短回答 每当有CPU操作要执行时,GUI线程触发的异步方法将在同一线程上执行。其他异步方法开始在调用线程上运行,并继续在线程池线程上运行

长话短说 SomeLongJobAsync开始在调用线程上执行打印的线程,她将绕山而上,直到到达等待。然后返回一个任务,该任务表示异步操作+该操作之后的继续。当整个操作完成时,任务将完成,除非由于异常或取消而提前完成


当Task.Delay1000本身正在执行时,因为不需要。当finally Task.Delay1000结束时,需要一个线程继续运行。它是哪个线程取决于默认情况下没有线程,因此该线程是线程池线程,但在GUI应用程序中,它是单GUI线程,等等。该线程执行代码的其余部分,直到它到达另一个异步点,即另一个等待点等等。

它在同一个线程上执行。说明:

async和await关键字不会导致创建其他线程。异步方法不需要多线程,因为异步方法不在自己的线程上运行。该方法在当前同步上下文上运行,仅当该方法处于活动状态时才使用线程上的时间。您可以使用Task.Run将CPU绑定的工作移动到后台线程,但后台线程对只等待结果可用的进程没有帮助


要认识到的重要一点是,异步不是创建线程,而是用返回延续的调用替换以前的阻塞调用。当线程被放置在队列中时,它会被阻塞,在它阻塞的东西变为可用、超时或异常等之前,它什么也不能做。阻塞UI线程是一件可怕的事情

相反,一个延续包含在程序中某个点捕获的足够信息,线程可以在以后从该点继续延续。显然,在所有异步调用中都需要一些特殊的东西才能使其工作,但它就是这样做的。它将线程状态冻结为一个延续,将其停在某个位置,立即返回,因此不会阻塞,然后在稍后获得所需信息时以某种方式从该状态重新开始

因此,您可以假设异步方法和长作业都将在同一个线程上完成,而不是同时完成,并且操作系统将选择一个好时机来决定何时做出这些选择

在实践中,具有消息泵UI线程的线程与其他线程之间存在差异,并且有可能将工作移动到不同的线程,并且任务、SynchronizationContext和线程池中有各种功能来支持更高级的场景

但我认为回答你的问题和让你理解的关键是对新事物的微妙使用,称为延续,以及它如何在某个时间捕获程序的状态以供以后使用。延续在函数式语言中已经使用了很长时间,并且在某些方面与其他语言中的未来和承诺的概念有关。用这些术语思考之后,您就可以完全忘记线程了。

SomeLongJobAsync开始在调用它的线程上执行,如果有,则当前SynchronizationContext将保存在等待机制生成的状态机中

在第一次等待完成后,将在当前SynchronizationContext上发布该方法的继续。在GUI应用程序中,这意味着继续在UI线程上执行。在控制台应用程序中,没有SyncrhronizatonContext,因此继续在线程池线程上执行

您可以通过在程序执行时打印Thread.CurrentThread的ManagedThreadId来检查这一点。考虑一下我从LIQPAD上的控制台应用程序运行的代码的修改版本:

private async void btnSlowPoke_Click(object sender, EventArgs e)
{
    await DoItAsync();
}

private async Task<int> SomeLongJobAsync()
{
    Console.WriteLine("Start SomeLongJobAsync, threadId = " + Thread.CurrentThread.ManagedThreadId);
    for (int x = 0; x < 9; x++)
    {
        //ponder my existence for one second
        await Task.Delay(1000);

        Console.WriteLine("Continue SomeLongJobAsync, threadId = " + Thread.CurrentThread.ManagedThreadId);
    }

    return 42;
}

public async Task<int> DoItAsync()
{   
    Console.WriteLine("She'll be coming round the mountain, threadId = " + Thread.CurrentThread.ManagedThreadId);
    Task<int> t = SomeLongJobAsync();  //<--On what thread does this execute?
    Console.WriteLine(" when she comes., threadId = " + Thread.CurrentThread.ManagedThreadId);
    return await t;
}

void Main()
{
    btnSlowPoke_Click(null, null);
    Console.ReadLine();
}
正如您所看到的,该方法在线程21上开始运行,但当每个等待完成时,它在线程池线程上继续运行,而不总是相同的线程。在这个例子中是11,12。如果在Windows窗体应用程序中运行此操作,则输出如下:

Windows窗体应用程序的输出:


您指的是GUI应用程序吗?或者一般地问?Where听起来不正确,因为Where听起来像是在组装机器上的处理器上。似乎有一些不一致
在这些答案中,有一个是不确定的。传唤斯蒂芬·克利里。克利里先生,你能听到我说话吗?我已经投了13票了。对于异步方法如何在捕获的上下文上恢复,他的回答有点让人困惑,但基本上是正确的。关键是延迟期间没有代码运行,因此延迟期间没有任何东西运行,也不需要线程;代码中唯一运行的部分是介于for循环变量递增并检查之间的位,这些部分在捕获的上下文中运行。它并不总是在线程池线程中运行。如果您在控制台应用程序中运行此代码,则它将是;但是,如果您在GUI线程中运行它(我假设这是由于单击处理程序),那么异步方法将始终在GUI线程上恢复。我在我的文章中详细解释了这种上下文捕获是如何工作的。这对我来说似乎是一个明确的答案,但似乎和其他人不一致。在进行更多讨论之前暂不作出判断。只有在LinqPad中没有SynchronizationContext,而GUI/ASP.Net线程模型中没有SynchronizationContext时才是如此。因此,对于GUI应用程序,Task.Delay1000的完成是否仍然从线程池请求线程,只是通过SynchronizationContext将等待继续发送到GUI线程。post?@avo yes。可能是像所有I/O操作一样的I/O完成端口线程。
She'll be coming round the mountain, threadId = 21
Start SomeLongJobAsync, threadId = 21
 when she comes., threadId = 21
Continue SomeLongJobAsync, threadId = 11
Continue SomeLongJobAsync, threadId = 11
Continue SomeLongJobAsync, threadId = 11
Continue SomeLongJobAsync, threadId = 11
Continue SomeLongJobAsync, threadId = 11
Continue SomeLongJobAsync, threadId = 12
Continue SomeLongJobAsync, threadId = 12
Continue SomeLongJobAsync, threadId = 12
Continue SomeLongJobAsync, threadId = 12
She'll be coming round the mountain, threadId = 8
Start SomeLongJobAsync, threadId = 8
 when she comes., threadId = 8
Continue SomeLongJobAsync, threadId = 8
Continue SomeLongJobAsync, threadId = 8
Continue SomeLongJobAsync, threadId = 8
Continue SomeLongJobAsync, threadId = 8
Continue SomeLongJobAsync, threadId = 8
Continue SomeLongJobAsync, threadId = 8
Continue SomeLongJobAsync, threadId = 8
Continue SomeLongJobAsync, threadId = 8
Continue SomeLongJobAsync, threadId = 8