线程,是操作系统中的术语,是操作系统进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。一个进程可以有很多线程,每条线程并行执行不同的任务。同一进程中的多条线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符和信号处理等等。但同一进程中的多个线程有各自的调用栈(call stack),自己的寄存器环境(register context),自己的线程本地存储(thread-local storage)。我们把用来执行用户任务的线程称为工作线程。而线程池,是一种成熟的线程使用模式。
为什么要创建线程池?
线程池属于对象池.所有对象池都具有一个非常重要的共性,就是为了最大程度复用对象.那么,线程池的最重要的特征也就是最大程度利用线程。所以线程池的目的就是为了减少创建和切换线程的额外开销,利用已经的线程多次循环执行多个任务从而提高系统的处理能力.
在ASP.NET工作进程中有两种线程池,Worker线程池处理所有传入的请求, I / O线程池处理的I / O(访问文件系统,Web服务和数据库等)。每个应用程序域都有其自己的线程池,可以排队到线程池的操作的数量只受可用内存的限制,然而,对线程池中的线程数的限制在这个过程中可以同时被激活。
当我们发出一个(异步)页面请求。一个Worker线程就会被从Worker线程池中取出, 这个Worker线程会触发I/O操作。当I/O操作开始时,另一个线程将会被从I/O线程池中取出,在收到I/O线程的返回值之前,Worker线程会一直处于闲置状态。所以,如果你的页面加载事件触发了多个I/O操作,那么,Worker线程就很可能会被闲置很长时间。而线程池能够把正处于闲置状态的Worker线程回收,使他能够为其他的页面请求提供服务。从而,降低系统开销。
只要并发请求的数量不超过线程池的可用线程的数量,一切都很好。但是,当你正在构建企业级应用程序时,你的线程池中的线程的数量就会达到极限。当这种情况发生时,新的请求会进入请求队列。 而ASP.NET支持在它开始拒绝请求并返回错误503服务不可用之前的最大请求数量,但是,我还没搞清楚这个具体的数量值是多少。
以下为多线程示例:
using System;using System.Collections.Generic;using System.Linq;using System.Web;using System.Web.UI;using System.Web.UI.WebControls;using System.Text;using System.Threading;using System.Data;using System.Data.SqlClient;using System.Configuration;namespace AsyncWeb{ public partial class ThreadAsync : System.Web.UI.Page { private string connect = ConfigurationManager.ConnectionStrings["ValvesTestConnectionString"].ConnectionString.ToString(); private DataTable threadOneResult; private DataTable threadTwoResult; private DataTable threadThreeResult; protected void Page_Load(object sender, EventArgs e) { AsyncCallsWithThreadPool(); } public void AsyncCallsWithThreadPool() { ThreadPool.QueueUserWorkItem(ThreadOne, 1000); ThreadPool.QueueUserWorkItem(ThreadTwo, 5000); ThreadPool.QueueUserWorkItem(ThreadThree, 10000); while (threadOneResult == null || threadTwoResult == null || threadThreeResult == null) { Thread.Sleep(10000); } // continue threadOneResult.Merge(threadTwoResult); threadOneResult.Merge(threadThreeResult); Output.DataSource = threadOneResult; Output.DataBind(); } private void ThreadOne(object state) { DataSet ds = new DataSet(); using (SqlConnection conn = new SqlConnection(connect)) { SqlCommand cmd = new SqlCommand("SELECT top 10 header_id, line_id, ordered_item FROM oe_order_lines_all", conn); SqlDataAdapter da = new SqlDataAdapter(cmd); da.Fill(ds); } threadOneResult = ds.Tables[0]; } private void ThreadTwo(object state) { DataSet ds = new DataSet(); using (SqlConnection conn = new SqlConnection(connect)) { SqlCommand cmd = new SqlCommand("SELECT top 20 header_id, line_id, ordered_item FROM oe_order_lines_all", conn); SqlDataAdapter da = new SqlDataAdapter(cmd); da.Fill(ds); } threadTwoResult = ds.Tables[0]; } private void ThreadThree(object state) { DataSet ds = new DataSet(); using (SqlConnection conn = new SqlConnection(connect)) { SqlCommand cmd = new SqlCommand("SELECT top 30 header_id, line_id, ordered_item FROM oe_order_lines_all", conn); SqlDataAdapter da = new SqlDataAdapter(cmd); da.Fill(ds); } threadThreeResult = ds.Tables[0]; } }}
在这个例子中,我在线程池中放了三个线程。当程序启动时,这三个线程就会被从线程池中取出,然后分别执行对应的操作,如从数据库中取数据。这三个线程在执行时,主线程会处于闲置状态,直到这三个线程完成主线程才会继续执行。我这里是使用While循环来实现的,其实还可以用其他方式实现。使用这种方式对线程的控制程度比较高,要特别小心的使用。ASP.NET也为开发人员提供了它自己的异步编程模型,一起来看看吧!
ASP.NET 中的异步编程模型
ASP.NET提供的异步编程模型包含:异步页(Asynchronous Page),Asynchronous Module 和 Asynchronous WebService。使用Asynchronous WebService实现异步的方式比较普遍,技术文档比较多。所以,本篇就不对Asynchronous WebService进行介绍。好了,开始吧!
异步页(Asynchronous Page)
上图为普通页面和异步页面的生命周期事件。相对于普通的页面生存周期事件,异步页多了异步事件,上图绿色的部分就是异步事件在页面事件中的执行位置。我们可以很清楚的看到主线程被一分为二了,其实这里主线程还是在的,只不过被闲置了来等待异步线程的执行结束。开发人员可以通过在ASP.NET中注册(Begin,End)方法来实现异步操作。ASP.NET会调用Begin方法,在begin方法中去执行I/O操作,如执行数据库查询操作,ASP.NET将立即从Begin方法中返回而不会去等待返回值。ASP.NET只要检测到线程返回了,就会立即把这个线程放回到线程池中,然后,使用这个线程去处理其他的请求。
从Begin方法中返回的是一个IAsyncResult 接口,正是通过这个接口ASP.NET才知道Begin方法什么时候结束。当Begin方法结束时,ASP.NET会到线程池中取出另一个线程去调用end方法。在end方法中,我们可以对返回值进行处理。
从ASP.NET的角度看,这只是一个普通的请求,但是确由两个线程来执行。这会不会有什么问题呢?
创建异步页的步骤很简单,首先在.aspx文件加入Async=”True”,目的就是程序在Runtime时告诉ASP.NET这是个异步页。如果是处理数据的异步请求,要在Connection String 中加“Asynchronous Processing=true;”。
如下示例:
using System;using System.Collections.Generic;using System.Linq;using System.Web;using System.Web.UI;using System.Web.UI.WebControls;using System.Data;using System.Data.SqlClient;using System.Configuration;namespace AsyncWeb{ public partial class About : System.Web.UI.Page { private SqlConnection _connection; private SqlCommand _command; private SqlDataReader _reader; protected void Page_Load(object sender, EventArgs e) { PageAsyncTask task = new PageAsyncTask(BeginAsyncOperation, EndAsyncOperation, TimeoutAsyncOperation, null); RegisterAsyncTask(task); } IAsyncResult BeginAsyncOperation(object sender, EventArgs e, AsyncCallback cb, object state) { string connect = ConfigurationManager.ConnectionStrings["ValvesTestConnectionString"].ConnectionString.ToString(); _connection = new SqlConnection(connect); _connection.Open(); _command = new SqlCommand( "SELECT top 10 header_id, line_id, ordered_item FROM oe_order_lines_all", _connection); return _command.BeginExecuteReader(cb, state); } void EndAsyncOperation(IAsyncResult ar) { _reader = _command.EndExecuteReader(ar); DataTable dt = new DataTable(); dt.Load(_reader); Output.DataSource = dt; Output.DataBind(); } void TimeoutAsyncOperation(IAsyncResult ar) { // Called if async operation times out (@ Page AsyncTimeout) Label1.Text = "Data temporarily unavailable"; } } }
这是一个比较简单的例子。但是,ASP.NET实现异步页的步骤还是比较复杂的,至少比普通的页复杂很多。但是,从性能上考虑,新请求能够很快进入管道,并且不需要在应用程序请求队列中等待很长的时间,这样就使得整个应用程序的性能都得到了提高。
ASP.NET异步HttpHandlers
ASP.NET中第二种异步模式是使用HttpHandler, httphander通过文件的后缀名来确定请求类型然后再确定使用哪个处理方法。ASPX handler 用来处理来自.aspx页面的请求。, 简单的说,其实它就是一个实现了IHttpHandler接口的类,它包括IsReusable方法和ProcessRequest方法。ProcessRequest方法的主要工作就是处理HttpRequest并把它转化为HttpResponse, 再将HttpContext传到page中。
// Name this C# file HandlerTest.cs and compile it with the// command line: csc /t:library /r:System.Web.dll HandlerTest.cs.// Copy HandlerTest.dll to your \bin directory.using System.Web;namespace HandlerExample{ public class MyHttpHandler : IHttpHandler { // Override the ProcessRequest method. public void ProcessRequest(HttpContext context) { context.Response.Write("This is an HttpHandler Test.
"); context.Response.Write("Your Browser:
"); context.Response.Write("Type: " + context.Request.Browser.Type + ""); context.Response.Write("Version: " + context.Request.Browser.Version); } // Override the IsReusable property. public bool IsReusable { get { return true; } } }}/*______________________________________________________________To use this handler, include the following lines in a Web.config file.*/