Home Articles

Threads有多不同的异步编程?

Asked
Viewed 1330 times
34

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

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

所以我想要理解的是,如果我们不使用任何线程进行并发执行,它将如何成为 async ? "you're not always using a thread."是什么意思?

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

  • 你有UI线程来接受输入,给出输出 .

  • 您可以处理UI线程中的内容,但它会使UI无响应 .

  • 因此,假设我们有一个与流相关的操作,我们需要下载某种数据 .

  • 我们还允许用户在下载时执行其他操作 .

  • 我们创建了一个新的工作线程,用于下载文件并更改进度条 .

  • 一旦完成,没有任何事情要做,因此线程被杀死 .

  • 我们继续从UI线程 .

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

async 编程不一样吗?如果没有,有什么区别?我读到 async 编程使用 ThreadPool 从中拉出线程 .

3 Answers

  • 34

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

    TAP样本:

    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绑定操作(例如读取文件,数据库操作和网络读取)时花费更多资源 . 创建新线程有两个缺点,

    • 就像你提到的文章一样,有内存成本和CLR
      线程池的限制 .

    • 将发生上下文切换 . 另一方面,异步不会手动创建任何线程,并且当IO绑定操作返回时它不会有上下文切换 .

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

    enter image description here

    此图来自MSDN文章“Asynchronous Pages in ASP.NET 2.0”,它详细解释了旧的异步如何在ASP.NET 2.0中工作 .

    关于异步编程模型,请参阅第27章中Jeffrey Richter的文章“Implementing the CLR Asynchronous Programming Model ", also there are more detail on his book " CLR via Csharp 3rd Edition” .

  • 6

    异步编程不需要线程 .

    "Asynchronous"表示API不会阻止调用线程 . 这并不意味着有另一个阻塞的线程 .

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

    • 你有UI线程来接受输入,给出输出 .

    • 您可以处理UI线程中的内容,但它会使UI无响应 .

    • 因此,假设我们有一个与流相关的操作,我们需要下载某种数据 .

    • 我们还允许用户在下载时执行其他操作 .

    • We use asynchronous APIs to download the file. 无需工作线程 .

    • 异步操作将其进度报告回UI线程(更新进度条),并且还将其完成报告给UI线程(可以像任何其他事件一样响应它) .

    这显示了如何只涉及一个线程(UI线程),还有异步操作 . 您可以启动多个异步操作,但这些操作只涉及一个线程 - 没有线程被阻塞 .

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

    ASP.NET类似,但它没有主/ UI线程 . 相反,它为每个未完成的请求都有一个“请求上下文” . ASP.NET线程来自线程池,当它们处理请求时,它们进入“请求上下文”;当他们完成后,他们退出他们的“请求上下文”并返回到线程池 .

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

    因此,当您在ASP.NET中执行不完整的异步操作时,该线程将递增该计数器并返回 . ASP.NET知道请求isn 't complete because the counter is non-zero, so it doesn' t完成响应 . 线程返回到线程池,此时:没有线程处理该请求 .

    异步操作完成后,它会将 async 方法的其余部分调度到请求上下文 . ASP.NET获取其处理程序线程之一(可能是也可能不是执行 async 方法的早期部分的同一线程),计数器递减,并且线程执行 async 方法 .

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

    欲获得更多信息:

  • 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 []中的字节之后,你不能拥有代码 .

Related