当异步代码尝试在已经执行的线程上恢复时会发生什么?

时间:2017-11-13 17:26:02

标签: c# .net multithreading asynchronous

我觉得这个问题的答案是由于我对线程的工作原理有不正确的概念,但是这里有。

private void button1_Click(object sender, EventArgs e)
{
  this.TestMethodAsync();   // No await, i.e. fire and forget

  // ** Some code here to perform long running calculation (1) **
}

private async Task TestMethodAsync()
{
  // Some synchronous stuff

  await Task.Delay(1000);

  // ** Some code here to perform long running calculation (2) **
}

首先,我不会“开火并忘记”这样的异步方法(我会使用Task.Run),但我遇到的代码确实存在,而我正试图了解效果是什么。

在使用WindowsFormsSynchronizationContext的WinForms应用程序中,我对async和await的理解告诉我,当我单击button1时,该方法将在UI线程上同步启动。它会调用TestMethodAsync并同步运行,直到它到达等待状态。然后它将捕获上下文,启动Task.Delay任务,并将控制权交给调用者。由于我们没有等待此调用,button1_Click将继续在UI线程上开始执行计算(1)。

在某些时候,Task.Delay(1000)将完成。然后,继续将使用捕获的上下文运行TestMethodAsync方法的其余部分,在这种情况下,意味着将在UI线程上运行continuation。现在将开始执行计算(2)。

我们现在有两个独立的代码段,希望同时在同一个线程(UI线程)上运行。我对此的调查似乎表明,线程在两段代码之间来回切换,以便同时执行它们。

问题:

我对这里到底发生了什么很困惑。如何在已经运行其他代码的线程上恢复?是什么迫使线程在想要运行的两段代码之间切换?通常,当您尝试在已经运行其他代码的线程上恢复时会发生什么?

(我想这与我的点击事件首先在UI线程上运行的方式没有任何不同,因为我知道它在UI线程上运行,我知道UI线程也正在做其他事情事情,但我以前没想过这样的事情。)

2 个答案:

答案 0 :(得分:8)

这是你不明白的秘密:我给你 Windows消息循环

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    MSG msg;
    BOOL bRet;
    while(TRUE)
    {
        bRet = GetMessage(&msg, NULL, 0, 0);
        if (bRet <= 0) break;
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return msg.wParam;
}

这是您申请的实际“主要”;你只是看不到它,因为它隐藏在幕后。

无法想象一个更简单的循环。它从队列中获取消息。如果没有更多消息,则必须完成该程序。如果有消息,则它运行标准消息转换并调度消息,然后继续运行。

  

如何在已经运行其他代码的线程上恢复?

不是。 “在运行其他代码的线程上恢复”实际上是将消息放入队列。 DispatchMessage正在同步调用“其他代码”。完成后,它返回循环,轮询队列,消息指示接下来需要分派的代码。然后它会同步运行,直到它返回循环。

  

是什么迫使线程在想要运行的两段代码之间切换?

无。这不会发生。

  

通常,当您尝试在已经运行其他代码的线程上恢复时会发生什么?

描述需要运行延续的消息排队。

  

我想这与我的点击事件首先在UI线程上运行的方式没有任何不同,因为我知道它在UI线程上运行,我知道UI线程也在做其他事情,但我之前没想过这样。

开始考虑它。

点击事件完全相同。你的计划正在做点什么;你点击鼠标;点击处理程序不会中断UI线程并开始在其上运行新工作。相反,消息排队,当您的UI线程控制返回到消息循环时,最终会处理单击; DispatchMessage导致通过Windows窗体中的某种机制调用Button1_OnClick。这就是WinForms ;用于将Windows消息转换为对C#方法的调用的机制。

但你已经知道了。您知道当事件驱动的程序执行长时间运行的同步操作时,UI会冻结,但最终会处理该单击事件。你是怎么想的那样发生的?你必须在某种程度上理解他们正在排队等待处理,对吗?

练习:DoEvents做什么?

练习:鉴于您现在所知道的:如果您在循环中调用DoEvents来解锁您的UI,可能会出现什么问题?

练习:await与GUI应用程序中的DoEvents有何不同?

答案 1 :(得分:3)

  

如何在已经运行其他代码的线程上恢复?

需要专门设计来支持它。需要有一些框架允许线程接受工作,然后在稍后的某个时间执行该工作。

这是您的UI线程的工作方式。它有一个队列,每当你安排在UI线程中完成工作时,你就会在队列的末尾添加一个项目。然后,UI线程从队列中获取第一个项目,执行它,然后在完成后继续下一个项目,依此类推,直到您结束应用程序。

  

是什么迫使线程在想要运行的两段代码之间切换?

没什么,因为它没有这样做。它运行一个,然后当它完成时,它运行另一个。

  

通常,当您尝试在已经运行其他代码的线程上恢复时会发生什么?

有人写了一些自定义代码来专门做到这一点,在这种情况下,它会执行特别指示它做的任何代码,否则你不能。