异步编程和多线程之间的区别是什么

本文关键字:区别 是什么 之间 多线程 编程 异步 | 更新日期: 2023-09-27 18:29:34

我认为它们基本上是一样的——编写在处理器之间分配任务的程序(在拥有2个以上处理器的机器上)。然后我读到这篇文章,上面写着:

异步方法是非阻塞操作。等待异步方法中的表达式在等待的任务正在运行。相反,该表达式为其余部分签名的调用方返回控制async方法。

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

我想知道是否有人能帮我把它翻译成英语。它似乎区分了异步性(这是一个词吗?)和线程化,并暗示你可以有一个有异步任务但没有多线程的程序。

现在我理解了异步任务的概念,比如Jon Skeet的C#深度,第三版第467页上的例子

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

CCD_ 1关键字表示"无论何时调用此函数,都不会在需要完成其调用后的所有内容的上下文中调用"

换句话说,在一些任务的中间写它

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

,由于DisplayWebsiteLength()xy无关,将导致DisplayWebsiteLength()被执行";在背景中";,像

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

显然,这是一个愚蠢的例子,但我是正确的,还是完全困惑了?

(此外,我对为什么sendere从未在上述函数的主体中使用感到困惑。)

异步编程和多线程之间的区别是什么

您的误解非常常见。很多人都知道多线程和异步是一回事,但事实并非如此。

类比通常会有所帮助。你在餐馆里做饭。有人点了鸡蛋和烤面包。

  • 同步:先煮鸡蛋,然后再烤吐司
  • 异步、单线程:您可以开始烹饪鸡蛋并设置计时器。你开始烤面包,并设置计时器。当他们都在做饭的时候,你打扫厨房。定时器关了,你就把鸡蛋从火上拿下来,把烤面包从烤面包机里拿出来,端上桌
  • 异步、多线程:你再雇两个厨师,一个煮鸡蛋,一个烤吐司。现在你要解决的问题是协调厨师,这样他们在共享资源时就不会在厨房里相互冲突。你必须付钱给他们

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

这将有助于认识到许多任务不受处理器限制。对于与处理器绑定的任务,雇佣与处理器数量一样多的工作者(线程),为每个工作者分配一个任务,为每个工作人员分配一个处理器,并让每个处理器尽可能快地计算结果,这是有意义的。但对于不在处理器上等待的任务,您根本不需要分配工作人员。您只需等待结果可用的消息到达,然后在等待时做其他事情。当该消息到达时,您可以将已完成任务的继续安排为待办事项列表上的下一件事。

让我们更详细地看一下Jon的例子。会发生什么?

  • 有人调用DisplayWebSiteLength。谁我们不在乎
  • 它设置一个标签,创建一个客户端,并要求客户端获取一些东西。客户端返回一个对象,该对象表示获取某物的任务。这项任务正在进行中
  • 它在另一个线程上进行吗?可能不会。阅读斯蒂芬关于为什么没有线索的文章
  • 现在我们正在等待这项任务。会发生什么?我们检查任务在创建和等待之间是否已完成。如果是,则获取结果并继续运行。让我们假设它还没有完成我们将此方法的剩余部分注册为该任务的延续,并返回
  • 现在,控制权已返回给调用者。它做什么?不管它想要什么
  • 现在假设任务完成。它是怎么做到的?也许它正在另一个线程上运行,或者我们刚刚返回的调用者允许它在当前线程上运行到完成。无论如何,我们现在已经完成了一项任务
  • 已完成的任务再次要求正确的线程——可能是仅线程——运行任务的延续
  • 控制权立即传递回我们刚刚在等待时留下的方法。现在一个可用的结果,因此我们可以分配text并运行该方法的其余部分

这就像我的比喻一样。有人向你要文件。你把文件邮寄出去,然后继续做其他工作。当它到达邮件中时,你会收到信号,当你喜欢它时,你就完成剩下的工作流程——打开信封,支付快递费,等等。你不需要雇佣另一个工人来为你做这一切。

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

您不必担心多段代码同时接触相同的对象:每个函数都将在允许任何其他javascript在页面上运行之前完成运行。(更新:自从编写这篇文章以来,JavaScript添加了异步函数和生成器函数。这些函数并不总是在执行任何其他JavaScript之前运行完成:每当它们达到yieldasync0关键字时,它们就会将执行权交给其他JavaScript,并且可以在以后继续执行,类似于C#的async方法。

然而,当执行类似AJAX请求的操作时,根本没有代码在运行,因此其他javascript可以响应诸如点击事件之类的事件,直到该请求返回并调用与其相关的回调。如果AJAX请求返回时,其他事件处理程序中的一个仍在运行,则在它们完成之前不会调用其处理程序。只有一个JavaScript";线程";跑步,即使你可以有效地暂停你正在做的事情,直到你得到你需要的信息。

在C#应用程序中,在处理UI元素时也会发生同样的事情——只有在UI线程上时才允许与UI元素交互。如果用户点击了一个按钮,而你想通过从磁盘中读取一个大文件来做出响应,那么缺乏经验的程序员可能会犯在点击事件处理程序本身中读取文件的错误,这将导致应用程序";冻结";直到文件完成加载,因为在释放该线程之前,不允许再响应任何单击、悬停或任何其他与UI相关的事件。

程序员可以用来避免这个问题的一个选项是创建一个新的线程来加载文件,然后告诉该线程的代码,当加载文件时,它需要再次在UI线程上运行剩余的代码,这样它就可以根据在文件中找到的内容更新UI元素。直到最近,这种方法还很流行,因为它是C#库和语言使之变得容易的东西,但从根本上来说,它比实际情况更复杂

如果你想想CPU在硬件和操作系统层面读取文件时在做什么,它基本上是发出一条指令,将磁盘中的数据读取到内存中,并用";中断";当读取完成时。换句话说,从磁盘读取(或任何I/O)本质上是异步操作。线程等待I/O完成的概念是一种抽象,库开发人员创建它是为了使编程更容易。没有必要。

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

在添加async/await关键字之前,C#对回调代码的调用方式更加明显,因为这些回调是以与任务关联的委托的形式出现的。为了让您仍然可以使用...Async()操作,同时避免代码的复杂性,async/await抽象掉了这些委托的创建。但它们仍然存在于编译后的代码中。

因此,您可以让UI事件处理程序await执行I/O操作,从而释放UI线程来做其他事情,并在读取完文件后或多或少地自动返回到UI线程,而无需创建新线程。