异步编程和多线程之间有什么区别?

时间:2016-01-08 15:53:03

标签: c# multithreading asynchronous parallel-processing async-await

我认为它们基本上是相同的 - 编写在处理器之间分割任务的程序(在具有2个以上处理器的机器上)。然后,我正在阅读https://msdn.microsoft.com/en-us/library/hh191443.aspx,其中包含

  

异步方法旨在实现非阻塞操作。等待   异步方法中的表达式不会阻塞当前线程   等待的任务正在运行。相反,表达式表示其余部分   该方法作为一个延续并将控制权返回给调用者   异步方法。

     

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

我想知道是否有人可以为我翻译成英文。它似乎区分了异步性(是一个单词?)和线程,并暗示你可以拥有一个具有异步任务但没有多线程的程序。

现在我理解异步任务的想法,例如pg上的示例。 Jon Skeet的第46期 C#深度,第三版

async void DisplayWebsiteLength ( object sender, EventArgs e )
{
    label.Text = "Fetching ...";
    using ( HttpClient client = new HttpClient() )
    {
        Task<string> task = client.GetStringAsync("http://csharpindepth.com");
        string text = await task;
        label.Text = text.Length.ToString();
    }
}

async关键字表示&#34; 无论何时调用此函数,都不会在调用其调用后所有内容都需要完成的上下文中调用此函数。&# 34;

换句话说,将它写在某个任务的中间

int x = 5; 
DisplayWebsiteLength();
double y = Math.Pow((double)x,2000.0);

,因为DisplayWebsiteLength()xy无关,将导致DisplayWebsiteLength()被执行&#34;在后台&#34;,就像< / p>

                processor 1                |      processor 2
-------------------------------------------------------------------
int x = 5;                                 |  DisplayWebsiteLength()
double y = Math.Pow((double)x,2000.0);     |

显然这是一个愚蠢的例子,但我是正确还是我完全糊涂或者是什么?

(另外,我很担心为什么sendere在上述函数的正文中没有使用过。)

2 个答案:

答案 0 :(得分:376)

你的误解非常普遍。许多人被教导多线程和异步是相同的,但它们不是。

类比通常有帮助。你在一家餐馆做饭。鸡蛋和烤面包的订单。

  • 同步:你煮鸡蛋,然后你做烤面包。
  • 异步,单线程:启动鸡蛋烹饪并设置计时器。你开始吐司做饭,并设置一个计时器。虽然他们都在做饭,但你要清理厨房。当计时器响起时,你可以将鸡蛋从烤箱中取出,然后将烤面包机从烤面包机中取出并送达。
  • 异步,多线程:你雇两个厨师,一个煮蛋,一个煮烤面包。现在你遇到了协调厨师的问题,这样他们在共享资源时就不会在厨房里相互冲突。而你必须付钱。

现在,多线程只是一种异步才有意义吗? 线程是关于工人的;异步是关于任务的。在多线程工作流中,您可以将任务分配给工作人员。在异步单线程工作流程中,您有一个任务图表,其中某些任务取决于其他任务的结果;当每个任务完成时,它会调用代码来调度可以运行的下一个任务,给定刚刚完成的任务的结果。但是你(希望)只需要一个工人来执行所有任务,而不是每个任务一个工人。

有助于意识到许多任务不受处理器约束。对于处理器绑定的任务,雇用与处理器一样多的工作者(线程),为每个工作者分配一个任务,为每个工作者分配一个处理器,让每个处理器完成其他工作,除了将结果计算为尽快。但是对于没有在处理器上等待的任务,您根本不需要分配工作人员。您只需等待消息到达,结果可用,在您等待时执行其他操作。当该消息到达时,您可以安排继续完成的任务作为待办事项列表中的下一个要检查的内容。

让我们更详细地看一下Jon的例子。怎么了?

  • 有人调用DisplayWebSiteLength。谁?我们不在乎。
  • 设置标签,创建客户端,并要求客户端提取内容。客户端返回一个表示抓取任务的对象。这项任务正在进行中。
  • 是否正在另一个帖子上进行?可能不是。阅读Stephen's article了解没有线程的原因。
  • 现在我们等待任务。怎么了?我们检查任务是否在我们创建它之间完成并等待它。如果是,那么我们获取结果并继续运行。我们假设它还没有完成。 我们将此方法的其余部分注册为该任务的后续部分并返回
  • 现在控制权已返回给来电者。它有什么作用?无论想要什么。
  • 现在假设任务完成。它是怎么做到的?也许它在另一个线程上运行,或者我们刚刚返回的调用者允许它在当前线程上运行完成。无论如何,我们现在已完成任务。
  • 完成的任务要求正确的线程 - 再次,可能只是 线程 - 运行任务的继续。
  • 控件立即返回到我们刚刚离开的方法中。现在 结果可用,因此我们可以分配text并运行方法的其余部分。

就像我的比喻一样。有人问你要一份文件。您通过邮件发送文件,继续做其他工作。当它到达邮件时,您会收到信号,当您有这样的信息时,您会完成工作流程的其余部分 - 打开信封,支付运费,无论如何。你不需要聘请另一名工人为你做这一切。

答案 1 :(得分:14)

浏览器中的Javascript是没有线程的异步程序的一个很好的例子。

您不必担心同时触摸相同对象的多个代码片段:在允许任何其他javascript在页面上运行之前,每个函数都将完成运行。

但是,当执行类似AJAX请求的操作时,根本没有代码运行,因此其他javascript可以响应点击事件之类的事情,直到该请求返回并调用与之关联的回调。如果其中一个其他事件处理程序在AJAX请求返回时仍在运行,则它的处理程序不会被调用,直到它们完成为止。只有一个JavaScript&#34;线程&#34;尽管你可以有效地暂停你正在做的事情,直到你得到你需要的信息为止。

在C#应用程序中,只要您处理UI元素,就会发生同样的事情 - 当您进入UI线程时,您只能与UI元素进行交互。如果用户单击了一个按钮,并且您希望通过从磁盘读取大文件来进行响应,那么没有经验的程序员可能会错误地在单击事件处理程序本身内读取该文件,这会导致应用程序“冻结” #34;直到文件完成加载,因为在该线程被释放之前,不允许再响应任何更多点击,悬停或任何其他与UI相关的事件。

程序员可能会使用一个选项来避免这个问题,就是创建一个新线程来加载文件,然后告诉该线程的代码,当文件加载时,它需要在UI线程上运行剩余的代码再次这样它可以根据文件中的内容更新UI元素。直到最近,这种方法非常受欢迎,因为它使C#库和语言变得简单,但它从根本上说比以前更加复杂。

如果您考虑CPU在硬件/操作系统级别读取文件时的操作,它基本上会发出一条指令,将磁盘中的数据从内存读入内存,然后点击操作系统带有&#34;中断&#34;当读取完成时。换句话说,从磁盘(或任何I / O)读取本质上是异步操作。等待I / O完成的线程的概念是库开发人员创建的抽象,以便更容易编程。没有必要。

现在,.NET中的大多数I / O操作都有一个可以调用的相应...Async()方法,它几乎立即返回Task。您可以向此Task添加回调,以指定在异步操作完成时要运行的代码。您还可以指定要运行该代码的线程,并且可以提供异步操作可以不时检查的令牌,以确定您是否决定取消异步任务,从而使其有机会快速停止其工作优雅地

在添加async/await关键字之前,C#对于如何调用回调代码更加明显,因为这些回调是与您与任务关联的委托形式。为了仍然为您提供使用...Async()操作的好处,同时避免代码的复杂性,async/await抽象出这些代理的创建。但他们仍然在编译代码中。

因此,您可以让您的UI事件处理程序await进行I / O操作,释放UI线程以执行其他操作,并且一旦您自动返回到UI线程,或多或少地自动返回到UI线程读完文件后 - 无需创建新帖子。

相关问题