33

我一直在async这里阅读一些文章:http ://www.asp.net/web-forms/tutorials/aspnet-45/using-asynchronous-methods-in-aspnet-45 ,作者说:

当您进行异步工作时,您并不总是使用线程。例如,当您发出异步 Web 服务请求时,ASP.NET 不会在异步方法调用和等待之间使用任何线程。

所以我想了解的是,async如果我们不使用任何线程进行并发执行,它会变成什么?“你并不总是使用线程”是什么意思?

让我首先解释一下我对使用线程的了解(一个简单的例子,当然线程可以在除了 UI 和 Worker 方法之外的不同情况下使用)

  1. 你有 UI 线程来接受输入,给出输出。
  2. 您可以在 UI 线程中处理事情,但它会使 UI 无响应。
  3. 所以假设我们有一个与流相关的操作,我们需要下载某种数据。
  4. 我们还允许用户在下载时做其他事情。
  5. 我们创建一个新的工作线程来下载文件并更改进度条。
  6. 一旦完成,就没有什么可做的,所以线程被杀死了。
  7. 我们从 UI 线程继续。

我们可以根据情况在 UI 线程中等待工作线程,但在下载文件之前,我们可以使用 UI 线程做其他事情,然后等待工作线程。

async编程不一样吗?如果不是,有什么区别?我读到async编程用于ThreadPool从中提取线程。

4

3 回答 3

34

异步编程不需要线程。

“异步”意味着 API 不会阻塞调用线程。这并不意味着有另一个线程正在阻塞。

首先,考虑您的 UI 示例,这次使用实际的异步 API:

  1. 你有 UI 线程来接受输入,给出输出。
  2. 您可以在 UI 线程中处理事情,但它会使 UI 无响应。
  3. 所以假设我们有一个与流相关的操作,我们需要下载某种数据。
  4. 我们还允许用户在下载时做其他事情。
  5. 我们使用异步 API 来下载文件。不需要工作线程。
  6. 异步操作将其进度报告给 UI 线程(更新进度条),它还将其完成报告给 UI 线程(可以像任何其他事件一样对其进行响应)。

这显示了如何只涉及一个线程(UI 线程),但也有异步操作在进行。您可以启动多个异步操作,但只有一个线程参与这些操作 - 没有线程被阻塞。

async/await提供了一种非常好的语法来启动异步操作然后返回,并在该操作完成时让方法的其余部分继续。

ASP.NET 与此类似,只是它没有主/UI 线程。相反,它对每个不完整的请求都有一个“请求上下文”。ASP.NET 线程来自一个线程池,它们在处理请求时进入“请求上下文”;完成后,它们退出“请求上下文”并返回线程池。

ASP.NET 会跟踪每个请求的不完整异步操作,因此当线程返回线程池时,它会检查该请求是否正在进行任何异步操作;如果没有,则请求完成。

因此,当您await在 ASP.NET 中执行不完整的异步操作时,线程将增加该计数器并返回。ASP.NET 知道请求未完成,因为计数器不为零,因此它不会完成响应。线程返回线程池,此时:没有线程在处理该请求。

当异步操作完成时,它将方法的其余部分安排async到请求上下文中。ASP.NET 获取其处理程序线程之一(可能与执行该async方法的早期部分的线程相同,也可能不同),计数器递减,线程执行该async方法。

ASP.NET vNext 略有不同;整个框架对异步处理程序有更多支持。但大体概念是一样的。

了解更多信息:

于 2012-06-10T15:27:09.513 回答
6

第一次看到asyncawait时,我认为它们是异步编程模型的 C# 语法糖。我错了,asyncawait不止于此。这是一个全新的异步模式基于任务的异步模式,http://www.microsoft.com/en-us/download/details.aspx?id=19957是一篇不错的入门文章。大多数实现 TAP 的 FCL 类都是调用 APM 方法(BegingXXX() 和 EndXXX())。以下是 TAP 和 AMP 的两个代码快照:

水龙头样品:

    static void Main(string[] args)
    {
        GetResponse();
        Console.ReadLine();
    }

    private static async Task<WebResponse> GetResponse()
    {
        var webRequest = WebRequest.Create("http://www.google.com");
        Task<WebResponse> response = webRequest.GetResponseAsync();
        Console.WriteLine(new StreamReader(response.Result.GetResponseStream()).ReadToEnd());
        return response.Result;
    }

APM 样本:

    static void Main(string[] args)
    {
        var webRequest = WebRequest.Create("http://www.google.com");
        webRequest.BeginGetResponse(EndResponse, webRequest);
        Console.ReadLine();
    }

    static void EndResponse(IAsyncResult result)
    {
        var webRequest = (WebRequest) result.AsyncState;
        var response = webRequest.EndGetResponse(result);
        Console.WriteLine(new StreamReader(response.GetResponseStream()).ReadToEnd());
    }

最后这两个会是一样的,因为 GetResponseAsync() 在里面调用了 BeginGetResponse() 和 EndGetResponse()。当我们反射 GetResponseAsync() 的源代码时,我们会得到这样的代码:

task = Task<WebResponse>.Factory.FromAsync(
       new Func<AsyncCallback, object, IAsyncResult>(this.BeginGetResponse), 
       new Func<IAsyncResult, WebResponse>(this.EndGetResponse), null);

对于 APM,在 BeginXXX() 中,有一个回调方法的参数,该方法将在任务(通常是 IO 繁重的操作)完成时调用。创建一个新线程并且是异步的,它们都会立即在主线程中返回,它们都是未阻塞的。在性能方面,在处理 I/O 绑定操作(例如读取文件、数据库操作和网络读取)时,创建新线程将消耗更多资源。创建新线程有两个缺点,

  1. 就像在您提到的文章中一样,内存成本和 CLR 是
    线程池的限制。
  2. 会发生上下文切换。另一方面,异步不会手动创建任何线程,也不会在 IO 绑定操作返回时进行上下文切换。

这是一张有助于理解差异的图片:

在此处输入图像描述

此图来自 MSDN 文章“ ASP.NET 2.0 中的异步页面”,其中非常详细地解释了旧的异步如何在 ASP.NET 2.0 中工作。

关于异步编程模型,请从 Jeffrey Richter 的文章“实现 CLR 异步编程模型”中获得更多详细信息,在他的书“CLR via Csharp 3rd Edition”第 27 章中也有更多详细信息。

于 2012-06-09T16:12:01.203 回答
1

假设您正在实现一个 Web 应用程序,当每个客户端请求进入您的服务器时,您需要发出一个数据库请求。当客户端请求进来时,线程池线程将调用您的代码。如果您现在同步发出数据库请求,线程将无限期地阻塞等待数据库响应结果。如果在此期间另一个客户端请求进来,线程池将不得不创建另一个线程,并且该线程将再次在它发出另一个数据库请求时阻塞。随着越来越多的客户端请求进来,创建的线程也越来越多,所有这些线程都阻塞等待数据库响应。结果是您的 Web 服务器分配了大量几乎没有使用的系统资源(线程及其内存)!更糟糕的是,当数据库确实回复各种结果时,线程变得畅通,它们都开始执行。但是由于您可能有很多线程在运行而 CPU 内核相对较少,因此 Windows 必须执行频繁的上下文切换,这会更加损害性能。这不是实现可扩展应用程序的方法。

为了从文件中读取数据,我现在调用 ReadAsync 而不是 Read。ReadAsync 在内部分配一个 Task 对象来表示读取操作的挂起完成。然后,ReadAsync 调用 Win32 的 ReadFile 函数 (#1)。ReadFile 分配它的 IRP,像在同步场景中一样初始化它(#2),然后将它传递给 Windows 内核(#3)。Windows 将 IRP 添加到硬盘驱动程序的 IRP 队列(#4),但现在,不是阻塞您的线程,而是允许您的线程返回您的代码;您的线程会立即从对 ReadAsync 的调用中返回(#5、#6 和 #7)。现在,当然,IRP 还不一定被处理,因此您不能在 ReadAsync 之后有代码尝试访问传入的 Byte[] 中的字节。

于 2014-11-10T16:16:00.970 回答