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

我认为它们基本上是同一件事——编写在处理器之间分配任务的程序(在有2个以上处理器的机器上)。然后我读到,它说:

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

async和await关键字不会导致额外的线程 创建。异步方法不需要多线程,因为Async 方法不在自己的线程上运行。该方法在电流上运行 对象时,才在线程上使用时间 方法是活动的。你可以使用Task。运行命令将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();
}
}

async关键字意味着“无论什么时候调用这个函数,在调用后的所有内容都需要补全的上下文中都不会被调用。

换句话说,就是在某个任务中编写它

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从未在上述函数体中使用感到困惑。)

152285 次浏览

你的误解非常普遍。许多人被教导多线程和异步是一回事,但事实并非如此。

打个比方通常会有帮助。你在餐厅做饭。一份鸡蛋和吐司的订单送来了。

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

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

认识到许多任务不受处理器限制会有所帮助。对于处理器绑定的任务,有必要雇用与处理器数量一样多的工作人员(线程),将一个任务分配给每个工作人员,将一个处理器分配给每个工作人员,并让每个处理器只做尽可能快的计算结果的工作。但是对于不等待处理器的任务,您根本不需要分配一个worker。你只需要等待消息到达,结果是可用的,并且在等待的时候做点别的事情。当消息到达时,你可以将已完成的任务作为待办事项列表上的下一件事来进行检查。

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

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

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

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

你不必担心多段代码同时触及相同的对象:每个函数将在任何其他javascript被允许在页面上运行之前完成运行。(更新:自编写以来,JavaScript添加了异步函数生成器函数。这些函数并不总是在任何其他javascript执行之前运行到完成:每当它们到达__ABC0或__ABC1关键字时,它们将执行交给其他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线程——而不必创建一个新线程。