在哪里等待'任务执行?

时间:2014-06-26 22:14:40

标签: 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
        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;
}
  1. 第一次写入DoItAsync()执行。
  2. SomeLongJobAsync()开始。
  3. WriteLine中的DoItAsync()执行。
  4. DoItAsync()暂停,SomeLongJobAsync()暂停,直至完成。
  5. SomeLongJobAsync()完成,因此DoItAsync()返回。
  6. 同时,用户界面响应迅速。

    SomeLongJobAsync()执行什么线程?

4 个答案:

答案 0 :(得分:9)

简答

只要有CPU操作执行async线程触发的GUI方法将在同一个线程上执行。其他async方法开始在调用线程上运行,并继续ThreadPool线程。

长答案

SomeLongJobAsync开始在调用线程上执行(打印&#34;她将来到山上&#34; ),直到达到{ {1}}。然后返回一个表示异步操作+之后的继续的任务。当整个操作完成后,任务将完成(除非由于异常或取消而提前完成)。

await本身是&#34;执行&#34; there is no thread,因为不需要。当最后Task.Delay(1000)结束时,需要一个线程 来继续。它所依赖的线程取决于SynchronizationContext(默认情况下, none ,因此该线程是Task.Delay(1000)线程,但在ThreadPool应用程序中它是&#39;单一的GUI线程,更多here)。该线程执行代码的其余部分,直到它到达另一个异步点(即另一个GUI),依此类推。等等。

答案 1 :(得分:1)

要意识到的重要一点是async不是关于创建线程,而是关于将一​​个阻塞调用替换为一个返回延续的调用。一个线程在被放置在一个队列上时就会阻塞它,然后它就可以做什么,直到它被阻塞的东西变得可用(或超时或异常等)。阻止UI线程是一件非常糟糕的事情。

相比之下,延续包含在程序中的某个点捕获的足够信息,以便线程在稍后的某个时间点继续(“继续”)。显然,所有Async调用都需要特殊的东西才能工作,但这就是它的作用。它将线程状态冻结为一个延续,在某处停放,立即返回(因此没有阻塞),然后(不知何故)稍后在所需信息可用时从该状态再次启动。

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

实际上,具有消息泵(UI线程)的线程与其他线程之间存在差异,并且有可能将工作移动到不同的线程,并且Task中有各种功能, SynchronizationContext和线程池以支持更高级的方案。

但我认为回答你的问题并让你理解的关键是这种微妙的使用新的称为延续的东西,以及它如何能够一次捕获程序的状态以供以后使用。连续性已经在函数式语言中使用了很长时间,并且在某些方面与其他语言中的期货和承诺的概念有关。一旦你按照这些术语思考,你就可以完全忘记线程。

答案 2 :(得分:1)

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

在第一个await完成后,方法的延续将发布在当前SynchronizationContext上。在GUI应用程序中,这意味着继续在UI线程上执行。在控制台应用程序中,没有SyncrhronizatonContext所以继续在线程池线程上执行。

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

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

从控制台应用输出:

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

正如您所看到的,该方法在线程21上启动了runnung,但是随着每个await的完成,它继续在线程池线程上而不是总是相同的。在这种情况下11,12。如果我在Windows窗体应用程序中运行它,则输出为:

Windows窗体应用程序的输出:

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

答案 3 :(得分:0)

它在同一个线程上执行。 The documentation解释说:

  

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

相关问题