第四章 ASP.NET中的线程与异步

4.1      线程基础

在Window3.1的时代,开始采用称为协同多任务的机制,实际上,Windows运行的多个程序并没有真的同时运行,每个程序都要在适当的时候释放CPU的控制权,以便其他的程序得到到执行的机会,这种机制称为协同。从Window NT开始采用抢先式多任务系统,每一个运行的程序都分配在一个独立的进程中,一个进程实际上是一个数据结构,描述运行这个程序所需要资源的信息。线程是包含在进程中的一种资源,本质上来说,线程也是一个特殊的数据结构,用于描述程序执行的状态信息。

4.1.1    线程

        OS运行一个程序的时候,操作系统将为这个准备运行的程序分配一个进程,以管理这个程序所需要的各种资源。在这些资源中,会包含一个称为主线程的线程数据结构,用来管理这个程序的执行状态。

线程这个数据结构包含以下内容:

线程的核心对象,其中主要包括线程当前的寄存器状态,当OS调度这个线程开始运行的时候,寄存器的状态被加载到CPU中,重新构建线程的执行环境,当线程被调度出来的时候,最后的寄存器状态被重新保存到这里,以备下一次执行的时候使用;

线程环境块(Thread environment block,TEB),是一块用户模式下的内存,包含线程的异常处理链的头部。另外,线程饿局部存储数据(Thread Local Storage Data)也存在这里;

用户模式的堆栈,用户程序的局部变量和参数传递所使用的堆栈,默认情况下,Windows将会分配1M的空间用于用户模式的堆栈;

内核模式堆栈,用于访问OS使用的堆栈;

在每一次的上下文切换时,W将执行下面的步骤:

1)将当前的CPU寄存器的值保存到当前运行的线程数据结构中,即其中的线程核心对象中;

2)选中下一个准备运行的线程,如果这个线程处于不同的进程中,那么,还必须首先切换虚拟地址空间;

3)加载准备运行线程的CPU寄存器状态到CPU中。

CLR中线程映射到OS的线程进行处理和调度,每创建一个线程将会消耗1M以上的内存空间。

线程的状态有Ready、Running、Suspended、Blocked、Sleep,各个状态之间的转换如下:


4.1.2    自定义线程

对于线程还可以分为两类:前台线程和后台线程。

前台线程能够保持程序的运行,当一个进程中所有的前台线程结束的时候,OS将立即结束这个程序进程。Notes:启动程序是所创建的主线程一定是前台线程。

后台线程不能保持程序的存活,这意味着当后台线程还没有完成的时候,程序也可能已经结束。当前台线程全部结束的时候,所有的后台线程都将被终止,而且不会有异常抛出。如果希望线程能够可靠完成,应该将这个线程设置为前台线程,对于不关键的任务,可以使用后台线程完成。在.NET环境下,从非托管代码进入托管执行环境的所有线程都被标记为后台线程。通过创建并启动新的Thread对象而生成的线程都默认为前台线程。线程对象的IsBackground属性是一个可读可写的属性,儿可以用来设置线程是前台还是后台线程,需要Notes:在线程启动之前进行设置,线程启动之后就不能设置了。

4.1.3    工作者线程和I/O线程

对于线程所执行的任务来说,可以将线程任务分为两种类型:工作者线程和I/O线程。工作者线程用来完成计算密集的任务,在任务的执行过程中,需要CPU不断的处理,所以,在工作者线程的执行过程中,CPU和线程的资源是充分利用的。I/O线程用于输入和输出工作。CPU在开始给任务的参数传递给设备,然后启动硬件设备即可。等到任务完成的时候,CPU收到一个通知,一般是硬件中断信号,CPU继续后续的处理工作。

4.1.4    线程池

线程是一个昂贵的资源,使用少量的线程来管理大量的网络连接,在启动输入输出处理之后,只使用一个线程监控网络通信的状况,在这种情况下,需要进行网络通信的线程在启动通信开始之后,就已经可以接受了,也就是说,可以被系统回收了。在通信的传输阶段,由于不需要CPU参与,可以没有线程介入,监控线程将负责在信息到达之后,重新启动一个计算密集的线程完成本地的处理工作。这样没有线程处于等待状态来消耗有限的内存资源。

对于I/O线程来说,可以将输入输出的操作分为三步:启动、实际输入输出、处理结果。可以将输入输出的操作分为两步来进行,以便充分利用线程资源。一般来说,在.NET中启动步骤的方法名称以Begin作为前缀,而处理结果的方法以End作为前缀,这两个方法可以运行在不同的线程上。

为了减少创建线程、销毁线程所带来的效率损失,同时也为了能够节约宝贵的内存,可以创建一个线程池,提供线程的工厂服务,这样就没有必要总是创建新的线程,而是当需要线程的时候从线程池中借出一个线程,当不再使用这个线程的时候,将这个线程归还给线程池,方便后续使用。


4.2      .NET中线程处理

在.NET中,线程通过定义在命名空间System.Threading中的Thread类进行描述,类的定义如下:

Publicsealed class Thread:CriticalFinalizerObject,_Thread

对于一个线程来说,主要包含以下资源信息:

ManagedThreadId,托管线程的唯一标识,可以通过它来区分不同的线程;

Name,一个有意义的名字,在调试程序的时候非常有用,但是只能设置一次;

IsAlive,是否活动线程;

ThreadState,提供比IsAlive更加详尽的线程状态,一般通过它来判断线程的状态;

ThreadPriority,线程运行的优先级;

IsBackground,是否为后台线程;

IsThreadPoolThread,是否为线程池管理的线程;

ExecutionContext,当前线程执行的上下文环境;

4.2.1    线程的创建与启动

由于线程用来表示代码的执行,所以,创建线程必须通过一些委托进行,常见的委托类型有三个:ThreadStart委托,ParameterizedThreadStart委托,TimerCallback委托。

1.ThreadStart委托表示一个没有参数的线程入口方法,这个方法必须没有参数,返回类型为void,public delegate voidThreadStart();

2.ParameterizedThreadStart委托能够在启动线程的时候提供一个参数,ParameterizedThreadStart委托运行我们为定义好的线程提供一个参数,当启动这个线程的时候,将实际的参数传递给这个线程。

Publicdelegate void ParameterizedThreadStart(Object obj);

创建线程:

    publicclass ThreadDemo
    {
        public void NoParam()
        {
           Console.WriteLine("Hi,FreshMan!");
        }
 
        public void HaveParam(object count)
        {
           Console.WriteLine("Hi,FreshMan,Craze!");
        }
 
        public static void TestThreadMeth()
        {
            ThreadDemo demo = new ThreadDemo();
            var threadNoParamter = newThread(demo.NoParam);
            threadNoParamter.Start();
 
            var threadWithParamter = newThread(demo.HaveParam);
            threadWithParamter.Start(5);
        }
    }


3.TimerCallback委托

我们还经常通过另外一些方法来隐式使用线程,一个经常使用线程的地方就是定时器,对于定时器来说,我们不直接创建一个Thread,而是通过Timer类来使用,它将通过线程池来获取线程。TimerCallback委托专门用于定时器的操作,这个委托允许我们定义一个定时任务,在指定的间隔之后重复调用,这个委托定义如下:

Publicdelegate void TimerCallback(object state);

1)定义可以在控制台窗口的左上角显示当前时间的方法,这个方法必须符合TimerCallback委托的定义:

    /// <summary>
    /// 在控制台的左上角打印时间
    /// </summary>
    public class WindowHelper
    {
        public static void ShowTime(objectuserData)
        {
            int oldX = Console.CursorLeft;
            int oldY = Console.CursorTop;
            Console.SetCursorPosition(0, 0);
           Console.Write(DateTime.Now.ToLongTimeString());
            Console.CursorLeft = oldX;
            Console.CursorTop = oldY;
        }
    }

2)定义一个定时器。

Timer类的构造函数定义如下:

PublicTimer(TimerCallback callback,Object state,long dueTime,long period)

Callback表示一个时间到达执行的委托,这个委托代表的方法必须符合委托TimerCallback的定义;

State表示当调用这个定时器委托时传递的参数;

dueTime表示从创建定时器到第一次调用延迟的时间,以毫秒为单位;

Period表示定时器开始之后,每次调用之间的时间间隔,以毫秒为单位;

这个定时器每秒执行一次:

varclock = new Timer(WindowHelper.ShowTime,null,0,1000);
Console.WriteLine("\nClockwill closer...");
Console.ReadKey();

4.2.2    线程的状态

线程的状态通过线程的ThreadState属性表示,这个属性的类型为System.Threading.ThreadState,这是一个枚举类型,可以表示如下的状态:

Running,运行状态;

StopRequested,线程正在被请求停止;

SuspendRequested,线程正在被请求暂停;

Background,这是一个背景线程;

Unstarted,还没有开始运行的线程;

Stopped,线程已经停止;

WaitSleepJoin,线程已经被阻止,可能是因为,调用Thread.Sleep或Thread.Join方法、请求锁定(通过调用Monitor.Enter或Monitor.Wait)或等待线程同步对象(ManualResetEvent)所造成。

Suspended,暂停的线程;

AbortRequested,已经在线程上调用了Thread.Abort方法,但是线程还没有收到试图结束线程的System.Threading.ThreadAbortException异常。

Aborted,线程在AbortRequested之后已经死掉,但是还没有转换为Stopped。

.net线程各个状态之间的转换关系:


SuspendRequested和Suspended这两个状态用灰色表示,这是由于不推荐使用了。

4.2.3    线程的执行上下文

线程的ExecutionContext属性的类型如下;

Publicsealed class ExecutionContext:ISerializable

借助于线程的上下文,可以使得在通过父线程创建的子线程中,可以访问父线程的线程敏感的数据。线程上下文中的数据在默认情况下,可以流动到这个线程的子线程中,如果不希望线程上下文中的数据流动,那么可通过线程对象的SuppressFlow方法抑制线程上下文的流动。

Console.WriteLine("MainThread Id:{0}",Thread.CurrentThread.ManagedThreadId);
            Thread.Sleep(10);

 //在父线程中设置线程相关的数据

           CallContext.LogicalSetData("Name","FreshMan");

            ThreadPool.QueueUserWorkItem(state=>Console.WriteLine("Name:{0}",CallContext.LogicalGetData("Name")));

 

           Console.WriteLine("IsFlowSuppressed:{0}",ExecutionContext.IsFlowSuppressed());

            //取消上下文流动

            ExecutionContext.SuppressFlow();

            ThreadPool.QueueUserWorkItem(state=> Console.WriteLine("Name:{0}", CallContext.LogicalGetData("Name")));

          

4.2.4    异步编程模式APM

对于不需要CPU参与的输入输出操作,可以将实际的处理步骤分为以下三步:

1)启动处理;

2)实际的处理,不需要CPU参与;

3)任务完成后的处理;

在这三个步骤中,只有第一步和第三步有CPU的参与:


当在一个线程上完成第一步操作的时候,不在等待下去,而是执行其他的任务,等到原来任务的第二步操作完成之后,通过启动第二个线程完成第三步操作,就不会有处于等待状态的线程了。在第一步的处理中,我们可以将第三步处理的委托保存起来,当第二步完成之后,取出保存的委托,重新取得一个空闲的线程来继续第三步的操作,这个线程完全可以是一个其他的线程,不需要必须是第一步的线程。主线程中启动第一步,第一步启动后,主线程结束,第二步不需要CPU参与,第二步完成之后,在第二个线程上启动第三步,完成之后第二个线程结束。这种处理模式称为异步编程模式简称APM。

大都以Begin和End作为前缀和后缀的方法,当调用Begin前缀的方法时,将会完成启动异步操作的第一步,同时,将会保存第三步处理的回调方法。第三步操作将会在线程池的I/O线程上执行。APM模式下的读取方法分别为BeginRead和EndRead。同步Read方法:

Publicoverride int Read(byte[] array,int offset,int count);


        /// <summary>
        /// 异步读取文件
        /// </summary>
        public static void ReadFileAPM()
        {
            Console.WriteLine("主程序中,线程id:{0}",Thread.CurrentThread.ManagedThreadId);
            const string path ="123.txt";
            byte[] buffer = new byte[4096];
            var fs = newFileStream(path,FileMode.OpenOrCreate,FileSystemRights.Read,FileShare.Read,8,FileOptions.Asynchronous);
            var resulte =fs.BeginRead(buffer,0,4096,delegate(IAsyncResult ar)
            {
                Console.WriteLine("第三步操作中。线程id:{0}",Thread.CurrentThread.ManagedThreadId);
                int length = fs.EndRead(ar);
                string message =Encoding.UTF8.GetString(buffer, 0, length);
                Console.WriteLine(message);
                Console.WriteLine();
            },fs);
            Console.WriteLine("第一步操作已经完成,现在主线程继续进行!");
            Console.WriteLine("线程的id:{0}",Thread.CurrentThread.ManagedThreadId);
 
            Console.WriteLine("Notes:线程池线程是背景线程,主线程不能结束,回车后结束程序");
        }

    }

4.2.5    基于事件的异步编程模式EPM

除了使用IAsyncResulte来管理异步线程之外,还可以使用基于事件的异步编程模式EPM来处理异步问题。EPM是一个高级的异步处理模式,内部使用APM来完成异步处理,可以使程序员更加方便、简单的完成异步任务。支持基于事件的异步模式的类将有一个或多个名称后缀为Async的方法,同时,还会有一个相应的名为Completed后缀的事件,Async方法用于启动异步处理,而Completed事件将在异步处理完成之后通过事件来宣告异步处理的完成。需要注意的是,在使用EPM模式的时候,不管是完成了异步处理,还是在处理中出现异常或者终止异步处理,都必须要调用Completed处理程序。

4.2.6    异步线程的状态与同步问题

APM模式下的Read方法和BeginRead的返回类型为IAsyncResult,这是一个标识异步操作状态的接口。这个接口由包含异步操作的方法的类实现,是启动异步操作的方法的返回类型,当异步操作完成的时候,这个对象也将由系统传递给结束异步操作的方法,即启动异步操作时传递的AsyncCallback委托调用的方法。


实现这个接口的对象用来存储异步操作的状态信息,并提供一个同步对象以允许线程在操作完成的时候中止。

AsyncState是一个只读的Object类型的属性,表示在Begin方法中传递的用户自定义的参数对象;

AsyncWaitHandle是一个WaitHandler类型的同步句柄,可以通过WaitOne,WaitAny,WaitAll方法进行线程同步。这样可以不用轮询IsCompleted状态直到操作结束。

CompletedSynchronously这个属性很少使用,一般返回false;

IsCompleted是一个bool类型的属性,用于表示异步操作是否已经完成。当此属性为真的时候,可以安全丢弃为异步操作分配的资源。

对于APM操作来说,必须使用End方法来结束异步操作,否则会造成资源的泄露。

在主线程中,如何知道异步操作的进度,如何协调主线程与线程池线程之间的同步问题?

如果第二个线程是一个我们自己创建的自由线程,那么通过join方法,就可以让当前的线程暂停下来,等待指定的线程完成之后,再继续当前的线程。Join方法的用法:

Publicvoid Join()

使用APM模式的时候,并没有自己来创建第二个线程,所以我们还是回到IAsyncResult吧,通过启动异步操作的方法,得到一个IAsyncResult对象,通过他可以有三种方法使得第一个线程与第二个线程同步。

第一种方式,轮询方式,在启动线程池中的线程之后,在主线程中通过不断的轮询线程的IsCompleted属性来判断异步操作是否已经结束。在while循环中,通过sleep释放当前的线程以便系统完成一些必要的操作。

//等待第二个线程完成
While(!reslut.IsCompleted)
{
Thread.Sleep(10);
}


第二种方式,通过句柄WaitHandle等待第二个线程完成。在这种情况下,实际上第一个线程还是要等待任务的完成。

            Console.WriteLine("主程序中,线程id:{0}",Thread.CurrentThread.ManagedThreadId);
            const string path ="123.txt";
            byte[] buffer = new byte[4096];
            var fs = new FileStream(path,FileMode.OpenOrCreate, FileSystemRights.Read, FileShare.Read, 8,FileOptions.Asynchronous);
            var resulte = fs.BeginRead(buffer,0, 4096, null, null);
            Console.WriteLine("第一步操作已经完成,现在主线程继续进行!");
            Console.WriteLine("线程的id:{0}",Thread.CurrentThread.ManagedThreadId);
            resulte.AsyncWaitHandle.WaitOne();
            int length = fs.EndRead(resulte);
            string message =Encoding.UTF8.GetString(buffer,0,length);
            Console.WriteLine(message);
 
            Console.WriteLine("Notes:线程池线程是背景线程,主线程不能结束,回车后结束程序");


第三种方式,直接通过调用EndRead方法进入等待。

            Console.WriteLine("主程序中,线程id:{0}", Thread.CurrentThread.ManagedThreadId);
            const string path ="123.txt";
            byte[] buffer = new byte[4096];
            var fs = new FileStream(path,FileMode.OpenOrCreate, FileSystemRights.Read, FileShare.Read, 8,FileOptions.Asynchronous);
            var resulte = fs.BeginRead(buffer,0, 4096, null, null);
 
            int length = fs.EndRead(resulte);
            string message =Encoding.UTF8.GetString(buffer, 0, length);
            Console.WriteLine(message);
 
            Console.WriteLine("Notes:线程池线程是背景线程,主线程不能结束,回车后结束程序");


4.2.7    处理管道中的异步问题

将等待的线程从等待中解放出来,这样就可以不让线程消耗在等待中。在执行启动异步操作的时候,将结束异步操作的第三步方法同时保存起来,在第一步之后,立即结束第一个线程,而让等待发生在无线程参与的状态,等待处理完成之后,取出保存的第三步处理方法,重新获取一个线程,在这个线程上进行第三步的处理工作,完成之后,继续在这个线程上进行原来的后续步骤。注意这两个线程之间没有同步关系了:


    ///<summary>
    /// 使得第三步的工作和后续工作在一个线程上完成
    /// </summary>
    public class NextStep
    {
        private EndEventHandler _nextHandler;
 
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <paramname="handler"></param>
        public NextStep(EndEventHandlerhandler)
        {
            _nextHandler = handler;
        }
 
        /// <summary>
        /// 异步方法
        /// </summary>
        /// <paramname="ar"></param>
        public void AsyncCallback(IAsyncResultar)
        {
            Console.WriteLine("开始管道结束部分任务");
            Console.WriteLine("线程Id:{0}",Thread.CurrentThread.ManagedThreadId);
            //调用实际的结束处理程序
            _nextHandler(ar);
            Console.WriteLine("继续管道处理过程。");
        }
    }


实际的处理工作分在两个方法中,一个用于启动异步处理第一步的方法,另一个用于完成异步处理第三步的方法。

    ///<summary>
    /// 读取文件
    /// </summary>
    public class ReadFile
    {
        private byte[] buffer = new byte[2048];
        FileStream fs;
        /// <summary>
        /// 读文件
        /// </summary>
        /// <paramname="sender"></param>
        /// <paramname="e"></param>
        /// <paramname="cb"></param>
        /// <paramname="extraData"></param>
        /// <returns></returns>
        public IAsyncResultBeginReadFile(Object sender,EventArgs e,AsyncCallback cb,Object extraData)
        {
            Console.WriteLine("正在执行开始方法");
            Console.WriteLine("线程id:{0}",Thread.CurrentThread.ManagedThreadId);
            fs = newFileStream("123.txt",FileMode.OpenOrCreate);
            returnfs.BeginRead(buffer,0,2048,cb,null);
        }
 
        /// <summary>
        /// 完成读取
        /// </summary>
        /// <paramname="ar"></param>
        public void EndReadFile(IAsyncResultar)
        {
            Console.WriteLine("正在执行结束方法");
            Console.WriteLine("线程id:{0}",Thread.CurrentThread.ManagedThreadId);
            int length = fs.EndRead(ar);
            string message =Encoding.UTF8.GetString(buffer,0,length);
            Console.WriteLine(message);
            Console.WriteLine("结束方法执行完成\n");
        }
    }


处理管道中可以注册一组异步处理方法,这个管道将在两个线程上完成,但是仍热可以保证处理步骤的先后顺序:

    ///<summary>
    /// 管道中注册一组异步处理方法,这个管道将在两个线程上完成
    /// </summary>
    public class StepDemo
    {
        private BeginEventHandler_beginHandler;
        private EndEventHandler _endHandler;
 
        /// <summary>
        /// 添加一个事件
        /// </summary>
        /// <paramname="beginHandler"></param>
        /// <paramname="endHandler"></param>
        public void AddOnWork(BeginEventHandlerbeginHandler,EndEventHandler endHandler)
        {
            _beginHandler = beginHandler;
            _endHandler = endHandler;
        }
 
        /// <summary>
        /// 启动工作
        /// </summary>
        /// <paramname="UnUsed"></param>
        public void DoAsyncWork(object UnUsed)
        {
            //仅仅需要启动工作,启动之后线程直接结束
            DoAsyncBeginWork();
        }
 
        /// <summary>
        /// 主线程开始工作
        /// </summary>
        private void DoAsyncBeginWork()
        {
            Console.WriteLine("开始执行管道,开始部分工作。");
            Console.WriteLine("线程id:{0}",Thread.CurrentThread.ManagedThreadId);
            if (_beginHandler != null)
            {
                NextStep step = newNextStep(_endHandler);
                //保存异步的结果用于调用结束处理时使用
               _beginHandler(this,EventArgs.Empty,step.AsyncCallback,null);
            }
            Console.WriteLine("Notices:此时管道的开始部分已经执行完成了");
            Console.WriteLine();
        }
    }


4.3      线程池

在.NET中定义在System.Threading命名空间中的类ThreadPool完成线程池的管理,定义如下:

Publicstatic class ThredPool

在.NET每一个进程中维护一个应用程序域共享的线程池。线程池提供了线程请求队列,这样,可以使用少量的线程来满足大量的线程请求。

4.3.1    线程池的工作原理

线程池中的线程分为两类:计算密集的工作者线程和I/O线程;在线程池中存在着两个任务队列,计算密集的任务队列和I/O任务队列。根据任务的类别将不同的任务排入相应的队列中等待处理。在线程池中管理着若干的线程池线程,这些线程从相应的队列中获取排队的任务依次进行处理。当处理完成一个任务后,线程将会继续从队列中获取任务进行处理。如果没有任务处理,在等待一段时间之后,空闲的线程将会被销毁。

4.3.2    将工作者线程加入线程池

工作者线程来说,由于必须依赖CPU完成任务,所以,使用线程池中的工作者线程仅仅需要提供一个满足如下委托的方法即可:

Publicdelegate void WaitCallback(Objcet state);

State表示这个方法的一个自定义参数,如果不需要参数,可以在使用的时候直接传递一个null。

ThreadPool的QueueUserWorkItem方法将符合WaitCall委托的方法排入线程池的线程请求队列中,同事还可以为方法传递一个参数:

    ///<summary>
    /// 简单线程池
    /// </summary>
    public class SimpleThreadPool
    {
        /// <summary>
        /// 添加一个线程到线程池中
        /// </summary>
        public static void AddThreadIntoPool()
        {
            ThreadPool.QueueUserWorkItem(newWaitCallback(ThreadProc));
            Console.WriteLine("主线程在继续工作....");
            Thread.Sleep(1000);
            Console.WriteLine("主线程结束.");
        }
 
        /// <summary>
        /// 委托子线程完成的实际工作
        /// </summary>
        /// <paramname="stateInfo"></param>
        static void ThreadProc(ObjectstateInfo)
        {
            Console.WriteLine("来自线程池的线程");
        }
    }


4.3.3    将I/O线程加入线程池

对I/O线程来说,实现启动异步操作的方法和完成异步操作的方法。Begin开头的方法用于将线程排入I/O任务的等待队列,在异步操作完成后,End方法也可以通过Begin方法的参数排入线程池的I/O队列中。将一个I/O线程排入线程池的方法就是调用Begin开头的方法。

4.4 HttpApplication中的异步线程

4.4.1    ASP.NET中的线程池设置

每当服务器收到一个请求,HttpRuntime将从HttpApplication池中获取一个HttpApplication对象处理此请求,请求的处理过程将被排入线程池,对于ASP.NET来说,在Maching.config文件的processModel部分中可以设置线程池中的参数,常用的参数设置如表:

下面的程序获得默认情况下的设置:

          var config = WebConfigurationManager.OpenWebConfiguration(null);
            ProcessModelSectionprocessModSection = config.GetSection("system.web/processModel") asProcessModelSection;
           Console.WriteLine("MaxWorkThreads:{0},MinWorkerThreads:{1},MaxIOThreads:{2},MinIOThreads:{3}",processModSection.MaxWorkerThreads, processModSection.MinWorkerThreads,processModSection.MaxIOThreads, processModSection.MinIOThreads);
 
            var httpRuntimeSection =config.GetSection("system.web/httpRuntime") as HttpRuntimeSection;
           Console.WriteLine("MinFreeThreads:{0},MinLocalRequestFreeThreads:{1},AppRequestQueueLimit:{2}",httpRuntimeSection.MinFreeThreads,httpRuntimeSection.MinLocalRequestFreeThreads, httpRuntimeSection.AppRequestQueueLimit);



优化的第一原则:对每个请求尽可能使用一个线程完成处理。

4.4.2    异步步骤中的异步点

.net中处理采用管道的处理模式,必须保证处理步骤的逻辑顺序。如果处理过程涉及输入和输出操作,那么可以在启动输入输出任务之后,释放当前的线程,则这个线程就可以处理其他的请求。当I/O完成之后,可以从线程池中重新获取一个线程,处理输入输出完成之后的任务,然后继续整个的处理过程。

在HttpApplication处理管道中每一个事件的处理步骤,可以有两种方式进行处理。在同步的方式下,提供一个事件处理方法直接完成整个步骤;在异步方式下,对于这个处理步骤,可以提供两个方法,一个用于启动涉及I/O的处理步骤,一个用于I/O完成之后的处理步骤,这样,对于每一个需要等待的处理步骤,可以分出一个异步点,在这个异步点之前启动耗时的操作,然后直接结束当前的线程,在没有线程参与的情况下,进行这个耗时的输入输出任务,在任务完成之后,重新从线程池中获取一个线程来继续当前请求的处理。

在异步方式下,处理管道将不再连续使用一个线程完成,而是每个处理步骤都可能在一个线程上进行,所以,我们不能假定处理管道总是处于一个线程,而使用基于线程的特征。

4.5      异步处理程序

对于同步的处理程序,通过接口IHttpHandler中定义的方法ProcessRequest进行处理,这个方法定义如下:

VoidProcessRequest(HttpContext context){}

对于异步的处理程序,这个方法也同样被两个方法所代替,这就是定义在异步处理程序接口IHttpAsyncHandler中的两个方法BeginProcessRequest和EndProcessRequest。如果一个处理程序实现了异步的接口,那么HttpApplication将使用异步方法来调用这个处理程序。

4.5.1    异步处理程序接口

Publicinterface IHttpAsyncHandler:IHttpHandler
{
IAsyncResultBeginProcessRequest(HttpContext context,AsyncCallback cb,Object extraData);
VoidEndProcessRequest(IAsyncResult result)
VoidProcessRequest(HttpContext context)
BoolIsReusable{get;}
}


4.5.2    在处理程序中异步调用web服务

对于一个典型的web服务来说,我们需要在BeginProcessRequest方法的最后调用启动调用web服务的方法,然后,这个方法就可以结束了。系统随后在没有线程参与的情况下进行漫长的web服务调用,当服务调用完成之后,.net将会把EndProcessRequest排入线程池的I/O线程队列中。最后,EndProcessRequest将被调用。在这个方法中,我们通过调用结束web服务的方法来获取服务返回的数据。

    ///<summary>
    /// AsyncHandlerDel 的摘要说明
    /// </summary>
    public class AsyncHandlerDel :IHttpAsyncHandler
    {
        localhost.WebService ws = newlocalhost.WebService();
 
        public IAsyncResultBeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)
        {
            return ws.BeginHelloWorld(cb,context);
        }
 
        public voidEndProcessRequest(IAsyncResult result)
        {
            string time =ws.EndHelloWorld(result);
            HttpContext context = result.AsyncStateas HttpContext;
           context.Response.Output.WriteLine(time);
        }
 
        public void ProcessRequest(HttpContextcontext)
        {
            throw newNotImplementedException();
        }
 
        public bool IsReusable
        {
            get
            {
                return false;
            }
        }
    }


需要注意的是,由于当前线程的问题,在EndProcessRequest方法中不能通过HttpContext.Current来获取当前的请求处理上下文对象。在这个例子中,在BeginProcessRequest方法中通过extraData保存了当前的上下文对象引用,用于在EndProcessRequest方法中使用,在EndProcessRequest方法中通过IAsyncResult参数的AsyncState属性获得这个上下文对象的引用来完成输出操作。

4.6      异步页面

页面page是.net网站开发中最常用到的页面生成技术,从本质上讲,也是一个处理程序,主要用来生成HTML的特殊处理程序。这个处理程序默认情况下实现了IHttpHandler接口,可以看成一个同步的处理程序,如果需要实现异步的页面处理程序,不需要手工修改他所实现的接口,而是通过页面指令Async来指定。当设置true是,表示生成的页面类将实现接口IHttpAsyncHandler,这样这个页面对象将通过异步方法被访问。

publicclass Page : TemplateControl, IHttpHandler

{

}

@ Page Language="C#"AutoEventWireup="true" CodeFile="CruiseOrderAjax.aspx.cs"Inherits="CruiseTours_CruiseOrderAjax" Aysnc="true" %>

在这种情况下,页面处理的过程将被分为两部分:从开始到包括PreRender处理部分由IHttpAsyncHandler接口中的BeginProcessRequest方法处理;从PreRenderComplete到处理结束将由EndProcessRequest方法处理。在PreRender和PreRenderComplete之间就是异步页面的异步点。这个过程用来等待长时间异步操作的完成。

4.6.1    页面异步任务的启动和完成

页面对象的AndOnPreRenderCompleteAsync方法用来注册异步操作的两个方法,这两个方法将在异步点之前和异步点之后被调用。具体来说,注册的开始异步处理方法将在PreRender事件之后被调用,以启动异步的处理。然后,页面处理将结束,线程被归还回线程池。此时进入等待异步完成的阶段,当异步操作完成之后,将从线程池中获取一个线程,开始页面处理的结束部分,首先异步操作完成之后,将从线程池中获取一个线程,开始页面处理的结束部分,首先异步操作的完成方法将被调用,然后,完成页面处理的剩余部分。使用两个委托就是启动异步步骤的委托System.web.BeginEventHandler和完成异步步骤的委托System.Web.EndEventHandler。

4.6.2    异步页面任务

在2.0以后,可以通过PageAsyncTask在页面的处理中注册多个异步操作,每一个操作通过PageAsyncTask定义。

Publicsealed class PageAsyncTask

使用这个类的时候,页面的异步状态可以设置为True,也可以设置为false,当设置为false的时候,页面的处理线程将被阻塞,so,在使用异步页面任务的时候,应将页面设置为异步状态。

PublicPageAsnyncTask(BeginEventHandler beginHandler,EndEventHandlerendHandler,EndEventHandler timeoutHandler,Object state,bool executeInParallel)

页面对象的RegisterAsyncTask(PageAsyncTask task)

使用方式:

PageAsyncTask asyncTask1=newPageAsyncTask(slowTask1.OnBegin,slowTask1.OnEnd,slowTask1.OnTimeOut,”Async1”,true);

Page.RegisterAsyncTask(asyncTask1);

RegisterAsyncTask方法的调用必须在异步点之前,否则,页面将会抛出异常。

4.6.3    异步页面中访问Web服务的三种方式

可以有多种方式在异步页面中访问web服务,而线程不会被阻塞。其中,常见的三种方式如下:

使用AddOnPreRenderCompleteAsync方式;

使用PageAsyncTask完成异步;

通过web服务的异步事件进行处理;

三种方式通过不同的形式来使用异步模式编程,第一种方式就是经典的异步编程模式,第二种PageAsyncTask使用更加方便,第三种方式不仅提供了更加简单的使用方式,而且在异步完成的时候,还会将启动异步操作的线程上下文环境恢复回来,使得我们虽然在使用异步编程模式,但是好像还是运行在一个线程之上。


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值