当前位置:  开发笔记 > 编程语言 > 正文

有什么意义等待DoSomethingAsync

如何解决《有什么意义等待DoSomethingAsync》经验,为你挑选了2个好方法。

我试图围绕使用更新版本添加到.NET框架中的所有Async内容.我理解其中的一些,但说实话,我个人认为这不会使编写异步代码更容易.我觉得它在大多数时候比较混乱,实际上比我们在async/await出现之前使用的更传统的方法更难阅读.

无论如何,我的问题很简单.我看到很多像这样的代码:

var stream = await file.readAsStreamAsync()

这里发生了什么?这不等于只调用方法的阻塞变体,即

var stream = file.readAsStream()

如果是这样,在这里使用它有什么意义呢?它不会使代码更容易阅读,所以请告诉我我错过了什么.



1> Jakub Lortz..:

两次通话的结果都是一样的.

区别在于var stream = file.readAsStream()将阻止调用线程直到操作完成.

如果从UI线程在GUI应用程序中进行调用,则应用程序将冻结,直到IO完成.

如果在服务器应用程序中进行调用,则被阻止的线程将无法处理其他传入请求.线程池必须创建一个新线程来"替换"被阻塞的线程,这是很昂贵的.可扩展性将受到影响.

另一方面,var stream = await file.readAsStreamAsync()不会阻止任何线程.GUI应用程序中的UI线程可以使应用程序响应,服务器应用程序中的工作线程可以处理其他请求.

当异步操作完成时,OS将通知线程池,并且将执行该方法的其余部分.

为了使所有这些"魔法"成为可能,使用async/await的方法将被编译到状态机中.Async/await允许使复杂的异步代码看起来像同步代码一样简单.



2> Luaan..:

它使编写异步代码变得非常容易。正如您在自己的问题中指出的那样,似乎您正在编写同步变量-但它实际上是异步的。

要了解这一点,您需要真正了解异步和同步的含义。含义非常简单-序列中的同步意味着一个接一个。异步意味着乱序。但是,这还不是全部内容-这两个词本身几乎没有用,它们的大部分含义来自上下文。您需要问:关于什么同步,到底是什么?

假设您有一个需要读取文件的Winforms应用程序。在按钮单击中,您执行File.ReadAllText,然后将结果放在一些文本框中-一切正常。I / O操作相对于您的UI是同步的-等待I / O操作完成时,UI无法执行任何操作。现在,客户开始抱怨在读取文件时UI似乎挂起了几秒钟-Windows将应用程序标记为“无响应”。因此,您决定将文件读取委派给后台工作人员-例如,使用BackgroundWorkerThread。现在,您的I / O操作相对于UI是异步的,每个人都很高兴-您要做的就是提取工作并在自己的线程中运行它,是的。

现在,这实际上非常好-只要您一次只真正执行一次这样的异步操作即可。但是,这确实意味着您必须明确定义UI线程边界的位置-您需要处理适当的同步。当然,这在Winforms中非常简单,因为您可以Invoke将UI工作编组回UI线程-但是如果您需要在进行后台工作时重复与UI交互该怎么办?当然,如果您只想连续发布结果,可以使用BackgroundWorkers- ReportProgress但是如果您还想处理用户输入怎么办?

这样做的好处await是,您可以轻松地在后台线程上以及在同步上下文中(例如Windows Forms UI线程)进行管理:

string line;
while ((line = await streamReader.ReadLineAsync()) != null)
{
  if (line.StartsWith("ERROR:")) tbxLog.AppendLine(line);
  if (line.StartsWith("CRITICAL:"))
  {
    if (MessageBox.Show(line + "\r\n" + "Do you want to continue?", 
                        "Critical error", MessageBoxButtons.YesNo) == DialogResult.No)
    {
      return;
    }
  }

  await httpClient.PostAsync(...);
}

太好了-您基本上像往常一样编写了同步代码,但是就UI线程而言,它仍然是异步的。而且错误处理与所有同步代码完全相同- usingtry-finally并且朋友们都工作得很好。

好吧,所以您不必BeginInvoke在这里和那里洒,有什么大不了的?真正重要的是,您无需付出任何努力,实际上就开始对所有这些I / O操作使用真正的异步API。问题是,就操作系统而言,实际上并没有任何同步I / O操作-当您执行“同步”操作时File.ReadAllText,操作系统仅发布异步I / O请求,然后阻塞线程直到响应回来。显而易见,该线程在此期间无所事事-它仍然使用系统资源,为调度程序增加了很少的工作量。

同样,在典型的客户端应用程序中,这没什么大不了的。用户不在乎您是一个线程还是两个线程-差别并不大。服务器完全是另一回事。如果一个典型的客户端同时只有一个或两个I / O操作,则您希望服务器处理数千个操作!在典型的32位系统上,您只能在进程中容纳约2000个具有默认堆栈大小的线程-不是因为物理内存要求,而是因为耗尽了虚拟地址空间。64位进程并没有受到限制,但是仍然存在启动新线程并销毁它们的过程相当昂贵的问题,并且您现在正在为OS线程调度程序添加大量工作-只是为了使这些线程保持等待状态。

但是await基于代码的没有这个问题。它仅在执行CPU工作时占用一个线程-等待I / O操作完成不是 CPU工作。因此,您发出该异步I / O请求,并且您的线程返回到线程池。当响应到来时,将从线程池中获取另一个线程。突然,您的服务器只使用了几个线程(通常每个CPU核心大约两个),而不是使用数千个线程。内存需求降低,多线程开销显着降低,您的总吞吐量大大提高。

因此-在客户端应用程序中,await实际上仅仅是为了方便。在任何较大的服务器应用程序中,这都是必要的 -因为突然之间,“启动新线程”方法根本无法扩展。替代方法是使用await所有老式的异步API,这些API 不能像同步代码那样处理任何东西,并且处理错误非常繁琐和棘手。

推荐阅读
小色米虫_524
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有