在你的服务器端代码中使用线程和创建异步处理(4)

在两种情况下有如此的不同 , 最让人困扰的问题是响应 fast.aspx 的页面比原来要多 4 秒的时间 , 原来用非常短的时间 . 虽然这个例子是我们人为 , 但这个例子显示了当你有如此相关的响应慢的页面是最糟糕的情形之一 . 如果在你的应用中慢的页面由于行动迟缓影响了 CPU 的使用 , 除了增加更多的硬件设备 , 好像没有好的办法 . 然而 , 应用中的慢页面之所以慢是因为等待 non-CPU-bound 的操作完成 , 问题不在于 CPU 动力缺乏 , 事实上慢的请求进入到线程池中 , 所以其它的请求必须排队直到一个线程释放 .

如果查看系统的扩展性 , 其受到 asp.net 线程池不利的影响 , 所以你有很少的选择 . 你可以改变线程池的上限 . 正如你看到的 , 默认的工作线程和 I/O 线程其上限都是 25. 当然 , 提高上限是一种比较愚笨粗糙的做法 . 如果你能发现恰好的数量来保持 CPU 的高利用率并且只有在服务器真的非常忙时请求才被延时或拒绝 , 那你真是太幸运了 . 保持服务真正忙所需的线程数量并非静止的 , 要依赖于各种因素如需求和处理的复杂度而波动 .

另外一个潜在的方案是慎重的确认系统那些需要用时较多并且执行 non-CPU-bound 请求 , 然后分派独特线程来响应它们 , 解除原生的线程池的线程来响应额外的请求 . 这就是异步处理者 .

异步处理者

尽管大部分的 asp.net 的页面和处理是由来自同步线程 ( 来自进程级的线程池 ) 响应 , 但创建异步请求的处理者是可能的 . 异步处理者实现了 IHttpAsyncHandler 接口 . 该接口继承于 IHttpHandler:

public interface IHttpAsyncHandler : IHttpHandler

{

IAsyncResult BeginProcessRequest(HttpContext ctx,

AsyncCallback cb,

object obj);

void EndProcessRequest(IAsyncResult ar);

}

实现该接口必须实现另外来自 IHttpHandler 的两个标准的方法 . 第一个 , BeginProcessRequest, 被应用类调用代替直接调用 ProcessRequest. 它会让处理者发起一个新的线程去处理请求并且会立刻从 BeginProcessRequest 方法返回 , 通过一个引用来验证 IAsyncResult 实例 , 目的是让运行时间直到操作结束 . 另外一个方法是 EndProcessRequest, 当请求处理完成被调用 , 如果有必要会被用来清除任何原来被分派的资源 .

如果你做过 .Net FrameWork 下异步执行的工作 , 你应该晓得最简单和最被推荐的方式使用异步委托代理执行异步工作 . 调用异步代理通过调用 BeginInvoke 隐式使用来自进程级的线程池的线程来执行委托功能 . 使用异步委托调用来实现异步处理者事实上非常直接 , 如图 2 所示 .

1@ WebHandler Language="C#" 
2        Class="EssentialAspDotNet.HttpPipeline.AsyncHandler" 
using System;
using System.Web;
using System.Threading;
using System.Diagnostics;
using System.Reflection;

namespace EssentialAspDotNet.HttpPipeline
{
  public delegate void ProcessRequestDelegate(HttpContext ctx);

  public class AsyncHandler : IHttpAsyncHandler
  { 
    public void ProcessRequest(HttpContext ctx)
    {
       System.Threading.Thread.Sleep(2000);
       ctx.Response.Output.Write(
                    "AsyncDelegate, threaded={0}",
                    AppDomain.GetCurrentThreadId());
    }
    
    public bool IsReusable
    {
      get { return true;}
    }

    public IAsyncResult BeginProcessRequest(HttpContext ctx, 
                AsyncCallback cb, object obj)
    {
      ProcessRequestDelegate prg = 
               new ProcessRequestDelegate(ProcessRequest);
      // Fire-and-forget
      return prg.BeginInvoke(ctx, cb, obj);
    }

    public void EndProcessRequest(IAsyncResult ar)
    {
    }
  }
}

图二

我实施 IHttpAsyncHandler.BeginProcessRequest 调用 BeginInvoke 在我的一个 ProcessRequestDelegate 实例上 ,

ProcessRequestDelegate 已经被引用我的 ProcessRequest 方法初始化 . 这在一个分离的线程上执行 ProcessRequest 是有效的 , 从请求线程中分离出来 .

运行同样的压力测试 slow.aspx 页面 , 这次用我的新的 asyncDelegate.ashx 处理者代替 fast.aspx, 产生结果如下 , 平均响应时间 fast.aspx 为 4.14 秒 , asyncDelegate.ashx 为 7.86 秒 , 请求次数为 957 次 .

让人失望 , 对于 fast.aspx 来说响应时间并没有真正的提高 . 由于异步委托调用完成不让创建一个异步处理者 , 这是因为它来自同样的是进程级的 CLR 线程池 ( 响应请求 ). 然而一个主要的请求线程确实需要返回给线程池 , 另外一个线程被取出去执行异步委托 , 这意味着这有个用来响应额外请求空线程收获 , 所以处理者没有用 . 你会得到同样的问题 , 如果你用 ThreadPool.QueueUserWorkItem 去执行异步请求处理 , 以为它也是同样的方式取得线程 .

用定制线程异步处理者

Published At
Categories with Web编程
Tagged with
comments powered by Disqus