Ajax[转]C#综合揭秘——细说四线程(下)

引言

正文首要从线程的基本成效法,CLR线程池当中工作者线程与I/O线程的开销,并行操作PLINQ等多少个地方介绍二十四线程的花费。
其中委托的BeginInvoke方法以及回调函数最为常用。 而
I/O线程可能简单受到我们的大意,其实在开发二十四线程系统,更应有多留意I/O线程的操作。尤其是在ASP.NET开发当中,可能更加多少人只会小心在客户端应用Ajax或者在服务器端使用UpdatePanel。其实言之成理利用I/O线程在简报项目或文件下载时,能尽量下降IIS的压力。
并行编程是Framework4.0中大力推广的异步操作方式,更值得更透彻地学习。
希望本篇小说能对各位的读书钻研具有帮忙,当中有所错漏的地方敬请点评。

 

 

目录

一、线程的概念

二、线程的基础知识

三、以ThreadStart格局完毕多线程

四、CLR线程池的工小编线程

五、CLR线程池的I/O线程

六、异步
SqlCommand

七、并行编程与PLINQ

八、计时器与锁

 

 

五、CLR线程池的I/O线程

在前一节所介绍的线程都属于CLR线程池的劳动力线程,这一节早先为大家介绍一下CLR线程池的I/O线程

I/O
线程是.NET专为访问外部资源所设置的一种线程,因为访问外部资源平时要面临外界因素的熏陶,为了防患让主线程受影响而遥远处在阻塞状态,.NET为四个I/O操作都成立起了异步方法,例如:FileStream、TCP/IP、WebRequest、WebService等等,而且每个异步方法的应用格局都分外类似,都是以BeginXXX为初叶,以EndXXX截止,上面为大家逐一讲演。

 

5.1  异步读写 FileStream

内需在 FileStream 异步调用 I/O线程,必须使用以下构造函数建立 FileStream
对象,并把useAsync设置为 true。

FileStream stream = new FileStream ( string path, FileMode mode,
FileAccess access, FileShare share, int bufferSize,bool useAsync ) ;

个中 path 是文件的相对路径或相对路径; mode 确定如何打开或创立文件;
access 确定访问文件的方式; share 确定文件怎么着进程共享; bufferSize
是代表缓冲区大小,一般默许最小值为8,在起步异步读取或写入时,文件大小一般超越缓冲大小;
userAsync代表是或不是启动异步I/O线程。

注意:当使用 BeginRead 和 BeginWrite
方法在实践大气读或写时效劳更好,但对于少量的读/写,这个艺术速度可能比同步读取还要慢,因为举办线程间的切换须求大批量时日。

 

5.1.1 异步写入

FileStream中包含BeginWrite、EndWrite 方法可以启动I/O线程举办异步写入。

public override IAsyncResult BeginWrite ( byte[] array, int offset,
int numBytes, AsyncCallback userCallback, Object stateObject ) public
override void EndWrite (IAsyncResult asyncResult )

 

BeginWrite 再次来到值为IAsyncResult,
使用办法与寄托的BeginInvoke方法一般,最好就是运用回调函数,幸免线程阻塞。在最终七个参数中,参数AsyncCallback用于绑定回调函数;
参数Object用于传递外部数据。要专注一点:AsyncCallback所绑定的回调函数必须是带单个
IAsyncResult 参数的无再次回到值方法。
在例子中,把FileStream作为外部数据传递到回调函数当中,然后在回调函数中选取IAsyncResult.AsyncState获取FileStream对象,最终通过FileStream.EndWrite(IAsyncResult)截止写入。

Ajax 1😉

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             //把线程池的最大值设置为1000
 6             ThreadPool.SetMaxThreads(1000, 1000);
 7             ThreadPoolMessage("Start");
 8 
 9             //新立文件File.sour
10             FileStream stream = new FileStream("File.sour", FileMode.OpenOrCreate, 
11                                        FileAccess.ReadWrite,FileShare.ReadWrite,1024,true);
12             byte[] bytes = new byte[16384];
13             string message = "An operating-system ThreadId has no fixed relationship........";
14             bytes = Encoding.Unicode.GetBytes(message);
15 
16             //启动异步写入
17             stream.BeginWrite(bytes, 0, (int)bytes.Length,new AsyncCallback(Callback),stream);
18             stream.Flush();
19             
20             Console.ReadKey();
21         }
22 
23         static void Callback(IAsyncResult result)
24         {
25             //显示线程池现状
26             Thread.Sleep(200);
27             ThreadPoolMessage("AsyncCallback");
28             //结束异步写入
29             FileStream stream = (FileStream)result.AsyncState;
30             stream.EndWrite(result);
31             stream.Close();
32         }
33 
34         //显示线程池现状
35         static void ThreadPoolMessage(string data)
36         {
37             int a, b;
38             ThreadPool.GetAvailableThreads(out a, out b);
39             string message = string.Format("{0}\n  CurrentThreadId is {1}\n  "+
40                   "WorkerThreads is:{2}  CompletionPortThreads is :{3}",
41                   data, Thread.CurrentThread.ManagedThreadId, a.ToString(), b.ToString());
42             Console.WriteLine(message);
43         }
44     }

Ajax 2😉

由输出结果可以看到,在行使FileStream.BeginWrite方法后,系统将自动启动CLR线程池中I/O线程。

Ajax 3

 

5.1.2 异步读取

FileStream 中含有 BeginRead 与 EndRead 可以异步调用I/O线程举办读取。

public override IAsyncResult BeginRead ( byte[] array,int offset,int
numBytes, AsyncCallback userCallback,Object stateObject) public override
int EndRead(IAsyncResult asyncResult)

 

其使用方法与BeginWrite和EndWrite相似,AsyncCallback用于绑定回调函数;
Object用于传递外部数据。在回调函数只须求选拔IAsyncResut.AsyncState就可取得外部数据。EndWrite
方法会重临从流读取到的字节数量。

首先定义 FileData 类,里面包罗FileStream对象,byte[]
数组和尺寸。然后把FileData对象作为外部数据传到回调函数,在回调函数中,把IAsyncResult.AsyncState强制转换为FileData,然后经过FileStream.EndRead(IAsyncResult)截止读取。最后比较一下长短,若读取到的长度与输入的数据长度不一至,则抛出越发。

Ajax 4😉

 1      class Program  2      {  3          public class FileData  4          {  5              public FileStream Stream;  6              public int Length;  7              public byte[] ByteData;  8          }  9   10          static void Main(string[] args) 11          {        12              //把线程池的最大值设置为1000 13              ThreadPool.SetMaxThreads(1000, 1000); 14              ThreadPoolMessage("Start"); 15              ReadFile(); 16   17              Console.ReadKey(); 18          } 19   20          static void ReadFile() 21          { 22              byte[] byteData=new byte[80961024]; 23              FileStream stream = new FileStream("File1.sour", FileMode.OpenOrCreate,  24                                      FileAccess.ReadWrite, FileShare.ReadWrite, 1024, true); 25               26              //把FileStream对象,byte[]对象,长度等有关数据绑定到FileData对象中,以附带属性方式送到回调函数 27              FileData fileData = new FileData(); 28              fileData.Stream = stream; 29              fileData.Length = (int)stream.Length; 30              fileData.ByteData = byteData; 31               32              //启动异步读取 33              stream.BeginRead(byteData, 0, fileData.Length, new AsyncCallback(Completed), fileData); 34          } 35    36          static void Completed(IAsyncResult result) 37          { 38              ThreadPoolMessage("Completed"); 39   40              //把AsyncResult.AsyncState转换为FileData对象,以FileStream.EndRead完成异步读取 41              FileData fileData = (FileData)result.AsyncState; 42              int length=fileData.Stream.EndRead(result); 43              fileData.Stream.Close(); 44   45              //如果读取到的长度与输入长度不一致,则抛出异常 46              if (length != fileData.Length) 47                  throw new Exception("Stream is not complete!"); 48   49              string data=Encoding.ASCII.GetString(fileData.ByteData, 0, fileData.Length); 50              Console.WriteLine(data.Substring(2,22)); 51          } 52   53          //显示线程池现状 54          static void ThreadPoolMessage(string data) 55          { 56              int a, b; 57              ThreadPool.GetAvailableThreads(out a, out b); 58              string message = string.Format("{0}\n  CurrentThreadId is {1}\n  "+ 59                           "WorkerThreads is:{2}  CompletionPortThreads is :{3}", 60                           data, Thread.CurrentThread.ManagedThreadId, a.ToString(), b.ToString()); 61              Console.WriteLine(message);       62          } 63               64    }

Ajax 5😉

由输出结果可以看来,在利用FileStream.BeginRead方法后,系统将活动启动CLR线程池中I/O线程。

Ajax 6

 

只顾:如若您看看的测试结果正好相反:工小编线程为999,I/O线程为1000,那是因为FileStream的文书容量小于缓冲值1024所致的。此时文件将会三回性读取或写入,而系统将启动工小编线程而非I/O线程来拍卖回调函数。

 

 

5.2 异步操作TCP/IP套接字

在介绍 TCP/IP 套接字前先简单介绍一下 NetworkStream
类,它是用于网络访问的功底数据流。 NetworkStream
提供了某些个主意控制套接字数据的发送与吸收,
其中BeginRead、EndRead、BeginWrite、EndWrite
可以完成异步操作,而且异步线程是出自于CLR线程池的I/O线程。

public override int ReadByte () public override int Read (byte[]
buffer,int offset, int size)

public override void WriteByte (byte value) public override void Write
(byte[] buffer,int offset, int size)

public override IAsyncResult BeginRead (byte [] buffer, int offset,
int size,  AsyncCallback callback, Object state ) public override int
EndRead(IAsyncResult result)

public override IAsyncResult BeginWrite (byte [] buffer, int offset,
int size,  AsyncCallback callback, Object state ) public override void
EndWrite(IAsyncResult result)

 

若要创设 NetworkStream,必须提供已接连的
Socket。而在.NET中利用TCP/IP套接字不需求一贯与Socket打交道,因为.NET把Socket的超过一半操作都置身System.Net.TcpListener和System.Net.Sockets.TcpClient里面,那四个类大大地简化了Socket的操作。一般套接字对象Socket包涵一个Accept()方法,此措施能发出鸿沟来等待客户端的请求,而在TcpListener类里也包罗了一个相似的主意
public TcpClient
AcceptTcpClient()用于等待客户端的哀求。此措施将会回去一个TcpClient
对象,通过 TcpClient 的 public NetworkStream
GetStream()方法就能博得NetworkStream对象,控制套接字数据的发送与接收。

 

下边以一个事例表明异步调用TCP/IP套接字收发数据的历程。

第一在服务器端建立默许地址127.0.0.1用以收发新闻,使用此地点与端口500新建TcpListener对象,调用TcpListener.Start
侦听传入的接连请求,再选取一个死循环来监听新闻。

在ChatClient类蕴含有吸收音讯与发送音信多少个功效:当收到到客户端请求时,它会使用
NetworkStream.BeginRead
读取客户端音讯,并在回调函数ReceiveAsyncCallback中输出新闻内容,若接收到的新闻的大小小于1时,它将会抛出一个非凡。当新闻成功接到后,再利用
NetworkStream.BeginWrite 方法回馈音信到客户端

Ajax 7😉

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             //设置CLR线程池最大线程数
 6             ThreadPool.SetMaxThreads(1000, 1000);
 7    
 8             //默认地址为127.0.0.1
 9             IPAddress ipAddress = IPAddress.Parse("127.0.0.1");
10             TcpListener tcpListener = new TcpListener(ipAddress, 500);
11             tcpListener.Start();
12             
13             //以一个死循环来实现监听
14             while (true)
15             {   //调用一个ChatClient对象来实现监听
16                 ChatClient chatClient = new ChatClient(tcpListener.AcceptTcpClient());    
17             }
18         }
19     }
20 
21     public class ChatClient
22     {
23         static TcpClient tcpClient;
24         static byte[] byteMessage;
25         static string clientEndPoint;
26 
27         public ChatClient(TcpClient tcpClient1)
28         {
29             tcpClient = tcpClient1;
30             byteMessage = new byte[tcpClient.ReceiveBufferSize];
31            
32             //显示客户端信息
33             clientEndPoint = tcpClient.Client.RemoteEndPoint.ToString();
34             Console.WriteLine("Client's endpoint is " + clientEndPoint);
35             
36             //使用NetworkStream.BeginRead异步读取信息
37             NetworkStream networkStream = tcpClient.GetStream();
38             networkStream.BeginRead(byteMessage, 0, tcpClient.ReceiveBufferSize,
39                                          new AsyncCallback(ReceiveAsyncCallback), null);
40         }
41 
42         public void ReceiveAsyncCallback(IAsyncResult iAsyncResult)
43         {
44             //显示CLR线程池状态
45             Thread.Sleep(100);
46             ThreadPoolMessage("\nMessage is receiving");
47 
48             //使用NetworkStream.EndRead结束异步读取
49             NetworkStream networkStreamRead = tcpClient.GetStream();
50             int length=networkStreamRead.EndRead(iAsyncResult);
51 
52             //如果接收到的数据长度少于1则抛出异常
53             if (length < 1)
54             {
55                 tcpClient.GetStream().Close();
56                 throw new Exception("Disconnection!");
57             }
58 
59             //显示接收信息
60             string message = Encoding.UTF8.GetString(byteMessage, 0, length);
61             Console.WriteLine("Message:" + message);
62 
63             //使用NetworkStream.BeginWrite异步发送信息
64             byte[] sendMessage = Encoding.UTF8.GetBytes("Message is received!");
65             NetworkStream networkStreamWrite=tcpClient.GetStream();
66             networkStreamWrite.BeginWrite(sendMessage, 0, sendMessage.Length, 
67                                             new AsyncCallback(SendAsyncCallback), null);
68         }
69 
70         //把信息转换成二进制数据,然后发送到客户端
71         public void SendAsyncCallback(IAsyncResult iAsyncResult)
72         {
73             //显示CLR线程池状态
74             Thread.Sleep(100);
75             ThreadPoolMessage("\nMessage is sending");
76 
77             //使用NetworkStream.EndWrite结束异步发送
78             tcpClient.GetStream().EndWrite(iAsyncResult);
79 
80             //重新监听
81             tcpClient.GetStream().BeginRead(byteMessage, 0, tcpClient.ReceiveBufferSize,
82                                                new AsyncCallback(ReceiveAsyncCallback), null);
83         }
84 
85         //显示线程池现状
86         static void ThreadPoolMessage(string data)
87         {
88             int a, b;
89             ThreadPool.GetAvailableThreads(out a, out b);
90             string message = string.Format("{0}\n  CurrentThreadId is {1}\n  " +
91                   "WorkerThreads is:{2}  CompletionPortThreads is :{3}\n",
92                   data, Thread.CurrentThread.ManagedThreadId, a.ToString(), b.ToString());
93 
94             Console.WriteLine(message);
95         }
96     }

Ajax 8😉

而在客户端只是使用不难的开发方式,利用TcpClient连接受服务器端,然后调用NetworkStream.Write方法发送新闻,最终调用NetworkStream.Read方法读取回馈新闻

Ajax 9😉

 1         static void Main(string[] args)  2         {  3             //连接服务端  4             TcpClient tcpClient = new TcpClient("127.0.0.1", 500);  5   6             //发送信息  7             NetworkStream networkStream = tcpClient.GetStream();  8             byte[] sendMessage = Encoding.UTF8.GetBytes("Client request connection!");  9             networkStream.Write(sendMessage, 0, sendMessage.Length); 10             networkStream.Flush(); 11  12             //接收信息 13             byte[] receiveMessage=new byte[1024]; 14             int count=networkStream.Read(receiveMessage, 0,1024); 15             Console.WriteLine(Encoding.UTF8.GetString(receiveMessage)); 16             Console.ReadKey(); 17         }

Ajax 10😉

瞩目观察运行结果,服务器端的异步操作线程都是源于于CLR线程池的I/O线程

Ajax 11

 

5.3 异步WebRequest

System.Net.WebRequest 是 .NET 为兑现访问
Internet 的 “请求/响应模型” 而付出的一个 abstract
基类, 它首要有两个子类:FtpWebRequest、HttpWebRequest、FileWebRequest。当使用WebRequest.Create(string
uri)创立对象时,应用程序就可以按照请求协议判断完毕类来展开操作。FileWebRequest、FtpWebRequest、HttpWebRequest
各有其成效:FileWebRequest 使用 “file://路径”
的URI格局达成对本土资源和中间文件的请求/响应、FtpWebRequest
使用FTP文件传输协议落实文件请求/响应、HttpWebRequest
用于拍卖HTTP的页面请求/响应。由于使用办法相近似,下边就以常用的HttpWebRequest为例子介绍一下异步WebRequest的利用方法。

在应用ASP.NET开发网站的时候,往往会忽视了HttpWebRequest的选用,因为开发都假诺客户端是采纳浏览器等工具去阅读页面的。但一旦您对REST开发格局有所精晓,那对
HttpWebRequest
就应当十分熟识。它能够在路线参数、头文件、页面主体、Cookie
等多处地点投入请求条件,然后对苏醒数据举办适量处理。HttpWebRequest
包涵有以下多少个常用艺术用于拍卖请求/响应:

public override Stream GetRequestStream () public override WebResponse
GetResponse ()

public override IAsyncResult BeginGetRequestStream ( AsyncCallback
callback, Object state ) public override Stream EndGetRequestStream (
IAsyncResult asyncResult ) public override IAsyncResult BeginGetResponse
( AsyncCallback callback, Object state ) public override WebResponse
EndGetResponse ( IAsyncResult asyncResult )

里面BeginGetRequestStream、EndGetRequestStream
用于异步向HttpWebRequest对象写入请求音讯; 
BeginGetResponse、EndGetResponse
用于异步发送页面请求并拿走重临音讯。使用异步格局操作Internet的“请求/响应”,幸免主线程长时间处于等候状态,而操作期间异步线程是来源于CLR线程池的I/O线程。

注意:呼吁与响应无法使用同步与异步混合开发格局,即当呼吁写入使用GetRequestStream同步形式,即便响应使用BeginGetResponse异步方法,操作也与GetRequestStream方法在于同一线程内。

上面以简要的例子介绍一下异步请求的用法。

先是为Person类加上可系列化特性,在劳动器端建立Hanlder.ashx,通过Request.InputStream
获取到请求数据并把多少转载为String对象,此实例中数据是以 “Id:1”
的样式落到实处传送的。然后根据Id查找对应的Person对象,并把Person对象写入Response.OutStream
中返还到客户端。

在客户端先把 HttpWebRequird.Method 设置为
“post”,使用异步格局通过BeginGetRequireStream获取请求数据流,然后写入请求数据
“Id:1”。再选择异步方法BeginGetResponse
获取回复数据,最终把数量反体系化为Person对象出示出来。

瞩目:HttpWebRequire.Method默许为get,在写入请求前必须把HttpWebRequire.Method设置为post,否则在动用BeginGetRequireStream
获取请求数据流的时候,系统就会爆发 “无法发送所有此谓词类型的始末正文”
的不行。

Model

Ajax 12😉

 1 namespace Model
 2 {
 3     [Serializable]
 4     public class Person
 5     {
 6         public int ID
 7         {
 8             get;
 9             set;
10         }
11         public string Name
12         {
13             get;
14             set;
15         }
16         public int Age
17         {
18             get;
19             set;
20         }
21     }
22 }

Ajax 13😉

 

服务器端

Ajax 14😉

 1 public class Handler : IHttpHandler {  2   3     public void ProcessRequest(HttpContext context)  4     {  5         //把信息转换为String,找出输入条件Id  6         byte[] bytes=new byte[1024];  7         int length=context.Request.InputStream.Read(bytes,0,1024);  8         string condition = Encoding.Default.GetString(bytes);  9         int id = int.Parse(condition.Split(new string[] { ":" },  10                            StringSplitOptions.RemoveEmptyEntries)[1]); 11          12         //根据Id查找对应Person对象 13         var person = GetPersonList().Where(x => x.ID == id).First(); 14          15         //所Person格式化为二进制数据写入OutputStream 16         BinaryFormatter formatter = new BinaryFormatter(); 17         formatter.Serialize(context.Response.OutputStream, person); 18     } 19  20     //模拟源数据 21     private IList<Person> GetPersonList() 22     { 23         var personList = new List<Person>(); 24          25         var person1 = new Person(); 26         person1.ID = 1; 27         person1.Name = "Leslie"; 28         person1.Age = 30; 29         personList.Add(person1); 30         ........... 31         return personList; 32     } 33  34     public bool IsReusable 35     { 36         get { return true;} 37     } 38 }

Ajax 15😉

客户端

Ajax 16😉

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             ThreadPool.SetMaxThreads(1000, 1000);
 6             Request();
 7             Console.ReadKey();
 8         }
 9 
10         static void Request()
11         {
12             ThreadPoolMessage("Start"); 
13             //使用WebRequest.Create方法建立HttpWebRequest对象
14             HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(
15                                             "http://localhost:5700/Handler.ashx");
16             webRequest.Method = "post";
17            
18             //对写入数据的RequestStream对象进行异步请求
19             IAsyncResult result=webRequest.BeginGetRequestStream(
20                 new AsyncCallback(EndGetRequestStream),webRequest);
21         }
22 
23         static void EndGetRequestStream(IAsyncResult result)
24         {
25             ThreadPoolMessage("RequestStream Complete");
26             //获取RequestStream
27             HttpWebRequest webRequest = (HttpWebRequest)result.AsyncState;
28             Stream stream=webRequest.EndGetRequestStream(result);
29 
30             //写入请求条件
31             byte[] condition = Encoding.Default.GetBytes("Id:1");
32             stream.Write(condition, 0, condition.Length);
33 
34             //异步接收回传信息
35             IAsyncResult responseResult = webRequest.BeginGetResponse(
36                 new AsyncCallback(EndGetResponse), webRequest);
37         }
38 
39         static void EndGetResponse(IAsyncResult result)
40         {
41             //显出线程池现状
42             ThreadPoolMessage("GetResponse Complete");
43 
44             //结束异步请求,获取结果
45             HttpWebRequest webRequest = (HttpWebRequest)result.AsyncState;
46             WebResponse webResponse = webRequest.EndGetResponse(result);
47             
48             //把输出结果转化为Person对象
49             Stream stream = webResponse.GetResponseStream();
50             BinaryFormatter formatter = new BinaryFormatter();
51             var person=(Person)formatter.Deserialize(stream);
52             Console.WriteLine(string.Format("Person    Id:{0} Name:{1} Age:{2}",
53                 person.ID, person.Name, person.Age));
54         }
55 
56         //显示线程池现状
57         static void ThreadPoolMessage(string data)
58         {
59             int a, b;
60             ThreadPool.GetAvailableThreads(out a, out b);
61             string message = string.Format("{0}\n  CurrentThreadId is {1}\n  " +
62                   "WorkerThreads is:{2}  CompletionPortThreads is :{3}\n",
63                   data, Thread.CurrentThread.ManagedThreadId, a.ToString(), b.ToString());
64 
65             Console.WriteLine(message);
66         }
67     }

Ajax 17😉

从运行结果可以见到,BeginGetRequireStream、BeginGetResponse方法是应用CLR线程池的I/O线程。

Ajax 18

 

 

5.4
异步调用WebService

对照TCP/IP套接字,在选用WebService的时候,服务器端须要更复杂的操作处理,使用时间多次会更长。为了避免客户端长时间处在等候景况,在配备服务引用时选取“生成异步操作”,系统可以自动建立异步调用的办法。

以.NET
2.0原先,系统都是利用ASMX来规划WebService,而多年来WCF可说是酷热登场,下边就以WCF为例子简单介绍一下异步调用WebService的例子。

是因为系统可以自动生成异步方法,使用起来非常不难,首先在劳动器端建立服务ExampleService,里面含有方法Method。客户端引用此服务时,选拔“生成异步操作”。然后使用 BeginMethod 启动异步方法,
在回调函数中调用EndMethod为止异步调用。

服务端

Ajax 19😉

 1      [ServiceContract]  2      public interface IExampleService  3      {  4          [OperationContract]  5          string Method(string name);  6      }  7    8      public class ExampleService : IExampleService  9      { 10          public string Method(string name) 11          { 12              return "Hello " + name; 13          } 14      } 15   16      class Program 17      { 18          static void Main(string[] args) 19          { 20              ServiceHost host = new ServiceHost(typeof(ExampleService)); 21              host.Open(); 22              Console.ReadKey(); 23              host.Close(); 24           } 25      } 26   27  <configuration> 28      <system.serviceModel> 29          <services> 30              <service name="Example.ExampleService"> 31                  <endpoint address="" binding="wsHttpBinding" contract="Example.IExampleService"> 32                      <identity> 33                          <dns value="localhost" /> 34                      </identity> 35                  </endpoint> 36                  <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" /> 37                  <host> 38                      <baseAddresses> 39                          <add baseAddress="http://localhost:7200/Example/ExampleService/" /> 40                      </baseAddresses> 41                  </host> 42              </service> 43          </services> 44      </system.serviceModel> 45  </configuration>

Ajax 20😉

客户端

Ajax 21😉

 1      class Program
 2      {
 3          static void Main(string[] args)
 4          {
 5              //设置最大线程数
 6              ThreadPool.SetMaxThreads(1000, 1000);
 7              ThreadPoolMessage("Start");
 8              
 9              //建立服务对象,异步调用服务方法
10              ExampleServiceReference.ExampleServiceClient exampleService = new
11                                      ExampleServiceReference.ExampleServiceClient();
12              exampleService.BeginMethod("Leslie",new AsyncCallback(AsyncCallbackMethod), 
13                                          exampleService);  
14              Console.ReadKey();
15          }
16  
17          static void AsyncCallbackMethod(IAsyncResult result)
18          {
19              Thread.Sleep(1000);
20              ThreadPoolMessage("Complete");
21              ExampleServiceReference.ExampleServiceClient example =
22                  (ExampleServiceReference.ExampleServiceClient)result.AsyncState;
23              string data=example.EndMethod(result);
24              Console.WriteLine(data);
25          }
26  
27          //显示线程池现状
28          static void ThreadPoolMessage(string data)
29          {
30              int a, b;
31              ThreadPool.GetAvailableThreads(out a, out b);
32              string message = string.Format("{0}\n  CurrentThreadId is {1}\n  " +
33                    "WorkerThreads is:{2}  CompletionPortThreads is :{3}\n",
34                    data, Thread.CurrentThread.ManagedThreadId, a.ToString(), b.ToString());
35  
36              Console.WriteLine(message);
37          }
38      }
39  
40  <configuration>
41      <system.serviceModel>
42          <bindings>
43              <wsHttpBinding>
44                  <binding name="WSHttpBinding_IExampleService" closeTimeout="00:01:00"
45                      openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
46                      bypassProxyOnLocal="false" transactionFlow="false" 
47                      hostNameComparisonMode="StrongWildcard" maxBufferPoolSize="524288"
48                      maxReceivedMessageSize="65536" messageEncoding="Text" textEncoding="utf-8"
49                      useDefaultWebProxy="true" allowCookies="false">
50                      <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
51                          maxBytesPerRead="4096" maxNameTableCharCount="16384" />
52                      <reliableSession ordered="true" inactivityTimeout="00:10:00" enabled="false" />
53                      <security mode="Message">
54                          <transport clientCredentialType="Windows" proxyCredentialType="None"
55                            realm="" />
56                          <message clientCredentialType="Windows" negotiateServiceCredential="true"
57                              algorithmSuite="Default" />
58                      </security>
59                  </binding>
60              </wsHttpBinding>
61          </bindings>
62          <client>
63              <endpoint address="http://localhost:7200/Example/ExampleService/"
64                  binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_IExampleService"
65                  contract="ExampleServiceReference.IExampleService" 
66                  name="WSHttpBinding_IExampleService">
67                  <identity>
68                      <dns value="localhost" />
69                  </identity>
70              </endpoint>
71          </client>
72      </system.serviceModel>
73  </configuration>

Ajax 22😉

留神观望运行结果,异步调用服务时,回调函数都是运作于CLR线程池的I/O线程当中。

Ajax 23

归来目录

六、异步 SqlCommand

从ADO.NET
2.0从头,SqlCommand就新增了几个异步方法执行SQL命令。绝对于一块施行格局,它使主线程不须要等待数据库的归来结果,在动用复杂性查询或批量安插时将有效增进主线程的效能。使用异步SqlCommand的时候,请留心把ConnectionString
的 Asynchronous Processing 设置为 true 。

瞩目:SqlCommand异步操作的越发之处在于线程并不借助于CLR线程池,而是由Windows内部提供,那比选取异步委托更有功能。但一旦须求动用回调函数的时候,回调函数的线程如故是缘于于CLR线程池的工作者线程。

SqlCommand有以下多少个办法襄助异步操作:

public IAsyncResult BeginExecuteNonQuery
(……) public int EndExecuteNonQuery(IAsyncResult)

public IAsyncResult BeginExecuteReader(……) public SqlDataReader
EndExecuteReader(IAsyncResult)

public IAsyncResult BeginExecuteXmlReader (……) public XmlReader
EndExecuteXmlReader(IAsyncResult)

 

鉴于应用办法一般,此处就以 BeginExecuteNonQuery
为例子,介绍一下异步SqlCommand的利用。首先创设connectionString,注意把Asynchronous
Processing设置为true来启动异步命令,然后把SqlCommand.CommandText设置为
WAITFOR DELAY “0:0:3”
来虚拟数据库操作。再经过BeginExecuteNonQuery启动异步操作,利用轮询方式监测操作情状。最终在操作达成后拔取EndExecuteNonQuery落成异步操作。

Ajax 24😉

 1     class Program  2     {  3         //把Asynchronous Processing设置为true  4         static string connectionString = "Data Source=LESLIE-PC;Initial Catalog=Business;“+  5                                          "Integrated Security=True;Asynchronous Processing=true";  6   7         static void Main(string[] args)  8         {  9             //把CLR线程池最大线程数设置为1000 10             ThreadPool.SetMaxThreads(1000, 1000); 11             ThreadPoolMessage("Start"); 12  13             //使用WAITFOR DELAY命令来虚拟操作 14             SqlConnection connection = new SqlConnection(connectionString); 15             SqlCommand command = new SqlCommand("WAITFOR DELAY '0:0:3';", connection); 16             connection.Open(); 17  18             //启动异步SqlCommand操作,利用轮询方式监测操作 19             IAsyncResult result = command.BeginExecuteNonQuery(); 20             ThreadPoolMessage("BeginRead"); 21             while (!result.AsyncWaitHandle.WaitOne(500)) 22                 Console.WriteLine("Main thread do work........"); 23  24             //结束异步SqlCommand 25             int count= command.EndExecuteNonQuery(result); 26             ThreadPoolMessage("\nCompleted"); 27             Console.ReadKey(); 28         } 29  30         //显示线程池现状 31         static void ThreadPoolMessage(string data) 32         { 33             int a, b; 34             ThreadPool.GetAvailableThreads(out a, out b); 35             string message = string.Format("{0}\n  CurrentThreadId is {1}\n  "+ 36                    "WorkerThreads is:{2}  CompletionPortThreads is :{3}\n", 37                    data, Thread.CurrentThread.ManagedThreadId, a.ToString(), b.ToString()); 38             Console.WriteLine(message); 39         } 40     }

Ajax 25😉

小心运行结果,SqlCommand的异步执行线程并不属于CLR线程池。

Ajax 26

 

假使觉得选拔轮询形式过于坚苦,可以选用回调函数,但要注意当调用回调函数时,线程是出自于CLR线程池的工小编线程。

Ajax 27😉

 1     class Program
 2     {
 3         //把Asynchronous Processing设置为true
 4         static string connectionString = "Data Source=LESLIE-PC;Initial Catalog=Business;”+
 5                                          “Integrated Security=True;Asynchronous Processing=true";
 6         static void Main(string[] args)
 7         {
 8             //把CLR线程池最大线程数设置为1000
 9             ThreadPool.SetMaxThreads(1000, 1000);
10             ThreadPoolMessage("Start");
11 
12             //使用WAITFOR DELAY命令来虚拟操作
13             SqlConnection connection = new SqlConnection(connectionString);
14             SqlCommand command = new SqlCommand("WAITFOR DELAY '0:0:3';", connection);
15             connection.Open();
16             
17             //启动异步SqlCommand操作,并把SqlCommand对象传递到回调函数
18             IAsyncResult result = command.BeginExecuteNonQuery(
19                                        new AsyncCallback(AsyncCallbackMethod),command);
20             Console.ReadKey();
21         }
22 
23         static void AsyncCallbackMethod(IAsyncResult result)
24         {
25             Thread.Sleep(200);
26             ThreadPoolMessage("AsyncCallback");
27             SqlCommand command = (SqlCommand)result.AsyncState;
28             int count=command.EndExecuteNonQuery(result);
29             command.Connection.Close();
30         }
31 
32         //显示线程池现状
33         static void ThreadPoolMessage(string data)
34         {
35             int a, b;
36             ThreadPool.GetAvailableThreads(out a, out b);
37             string message = string.Format("{0}\n  CurrentThreadId is {1}\n  "+
38                   "WorkerThreads is:{2}  CompletionPortThreads is :{3}\n",
39                   data, Thread.CurrentThread.ManagedThreadId, a.ToString(), b.ToString());
40 
41             Console.WriteLine(message);
42         }
43     }

Ajax 28😉

运行结果:

Ajax 29

 

 

归来目录

七、并行编程与PLINQ

要运用二十二十四线程开发,必须格外熟习Thread的施用,而且在开发进程中或者会晤对不少茫然的题材。为了简化开发,.NET
4.0
尤其提供一个相互编程库System.Threading.Tasks,它可以简化并行开发,你无需间接跟线程或线程池打交道,就足以简简单单建立三十二线程应用程序。其余,.NET还提供了新的一组扩大方法PLINQ,它富有活动分析查询功效,假诺并行查询能增强系统功用,则同时运转,即使查询不可能从相互查询中收益,则按原顺序查询。下边将详细介绍并行操作的法子。

 

7.1 泛型委托

运用并行编程可以同时操作多个委托,在介绍并行编程前先简单介绍一下八个泛型委托System.Func<>与System.Action<>。

Func<>是一个能承受七个参数和一个重返值的泛型委托,它能接受0个到16个输入参数,
其中 T1,T2,T3,T4……T16 代表自定的输入类型,TResult为自定义的重返值。
public delegate TResult Func<TResult>() public delegate TResult
Func<T1,TResult>(T1 arg1) public delegate TResult
Func<T1,T2, TResult>(T1 arg1,T2 arg2) public delegate TResult
Func<T1,T2, T3, TResult>(T1 arg1,T2 arg2,T3 arg3) public
delegate TResult Func<T1,T2, T3, ,T4, TResult>(T1 arg1,T2
arg2,T3 arg3,T4 arg4) ………….. public delegate TResult
Func<T1,T2, T3, ,T4, …… ,T16,TResult>(T1 arg1,T2 arg2,T3
arg3,T4 arg4,…… ,T16 arg16)

Action<>与Func<>至极相似,差距在于Action<>的再次回到值为void,Action能接受0~16个参数
public delegate void Action<T1>() public delegate void
Action<T1,T2>(T1 arg1,T2 arg2) public delegate void
Action<T1,T2, T3>(T1 arg1,T2 arg2, T3 arg3) ………….
public delegate void Action<T1,T2, T3, ,T4, …… ,T16>(T1
arg1,T2 arg2,T3 arg3,T4 arg4,…… ,T16 arg16)

 

7.2 职责并行库(TPL)

System.Threading.Tasks中的类被统称为天职并行库(Task Parallel
Library,TPL),TPL使用CLR线程池把工作分配到CPU,并能自动处理工作分区、线程调度、撤除接济、状态管理以及其余低级其他细节操作,极大地简化了多线程的开发。

注意:TPL比Thread更具智能性,当它判断职责集并没有从互相运行中收益,就会选拔按顺序运行。但绝不所有的项目都符合选择并行开发,创建过多并行职务可能会危机程序的习性,下落运作作用。

TPL包蕴常用的数据交互与职分并行二种实施措施:

7.2.1 数据交互

多少交互的基本类就是System.Threading.Tasks.Parallel,它包括多少个静态方法
Parallel.For 与 Parallel.ForEach,
使用办法与for、foreach相仿。通过那三个形式可以并行处理System.Func<>、System.Action<>委托。

以下一个例子就是运用 public static ParallelLoopResult For( int from, int
max, Action<int>) 方法对List<Person>举行相互查询。
要是使用单线程形式查询3个Person对象,须要用时大概6秒,在动用并行形式,只需选择2秒就能已毕查询,而且可以逃脱Thread的累赘处理。

Ajax 30😉

 1     class Program  2     {  3         static void Main(string[] args)  4         {  5             //设置最大线程数  6             ThreadPool.SetMaxThreads(1000, 1000);  7             //并行查询  8             Parallel.For(0, 3,n =>  9                 { 10                     Thread.Sleep(2000);  //模拟查询 11                     ThreadPoolMessage(GetPersonList()[n]); 12                 }); 13             Console.ReadKey(); 14         } 15  16         //模拟源数据 17         static IList<Person> GetPersonList() 18         { 19             var personList = new List<Person>(); 20  21             var person1 = new Person(); 22             person1.ID = 1; 23             person1.Name = "Leslie"; 24             person1.Age = 30; 25             personList.Add(person1); 26             ........... 27             return personList; 28         } 29  30         //显示线程池现状 31         static void ThreadPoolMessage(Person person) 32         { 33             int a, b; 34             ThreadPool.GetAvailableThreads(out a, out b); 35             string message = string.Format("Person  ID:{0} Name:{1} Age:{2}\n" + 36                   "  CurrentThreadId is {3}\n  WorkerThreads is:{4}" + 37                   "  CompletionPortThreads is :{5}\n", 38                   person.ID, person.Name, person.Age, 39                   Thread.CurrentThread.ManagedThreadId, a.ToString(), b.ToString()); 40  41             Console.WriteLine(message); 42         } 43     }

Ajax 31😉

着眼运行结果,对象无须根据原排列顺序举行查询,而是利用并行情势查询。

Ajax 32

 

若想甘休操作,可以行使ParallelLoopState参数,上边以ForEach作为例子。
public static ParallelLoopResult ForEach<TSource>(
IEnumerable<TSource> source, Action<TSource,
ParallelLoopState> action)
其中source为数据集,在Action<TSource,ParallelLoopState>委托的ParallelLoopState参数当中富含有Break()和
Stop()三个方法都可以使迭代停止。Break的利用跟传统for里面的利用格局一般,但因为远在并行处理当中,使用Break并不可能有限支撑拥有运行能即时终止,在时下迭代以前的迭代会继续执行。若想立时停下操作,可以动用Stop方法,它能保险及时为止所有的操作,无论它们是处在当前迭代的前头依然后来。

Ajax 33😉

 1     class Program
 2     {
 3          static void Main(string[] args)
 4          {
 5              //设置最大线程数
 6              ThreadPool.SetMaxThreads(1000, 1000);
 7  
 8              //并行查询
 9              Parallel.ForEach(GetPersonList(), (person, state) =>
10                  {
11                      if (person.ID == 2)
12                          state.Stop();
13                      ThreadPoolMessage(person);
14                  });
15              Console.ReadKey();
16          }
17  
18          //模拟源数据
19          static IList<Person> GetPersonList()
20          {
21              var personList = new List<Person>();
22  
23              var person1 = new Person();
24              person1.ID = 1;
25              person1.Name = "Leslie";
26              person1.Age = 30;
27              personList.Add(person1);
28              ..........
29              return personList;
30          }
31  
32          //显示线程池现状
33          static void ThreadPoolMessage(Person person)
34          {
35              int a, b;
36              ThreadPool.GetAvailableThreads(out a, out b);
37              string message = string.Format("Person  ID:{0} Name:{1} Age:{2}\n" +
38                    "  CurrentThreadId is {3}\n  WorkerThreads is:{4}" +
39                    "  CompletionPortThreads is :{5}\n",
40                    person.ID, person.Name, person.Age,
41                    Thread.CurrentThread.ManagedThreadId, a.ToString(), b.ToString());
42  
43              Console.WriteLine(message);
44          }
45      }

Ajax 34😉

观测运行结果,当Person的ID等于2时,运行将会停下。

Ajax 35

 

当要在四个线程中调用本地变量,可以行使以下措施: public static
ParallelLoopResult ForEach<TSource, TLocal>(IEnumerable<Of
TSource>, Func<Of TLocal>, Func<Of
TSource,ParallelLoopState,TLocal,TLocal>, Action<Of TLocal>)
其中第四个参数为数据集;
第三个参数是一个Func委托,用于在每个线程执行前进行初叶化; 第
多少个参数是委托Func<Of
T1,T2,T3,TResult>,它能对数据集的各种成员进行迭代,当中T1是数据集的分子,T2是一个ParallelLoopState对
象,它可以控制迭代的情景,T3是线程中的本地变量;
第多少个参数是一个Action委托,用于对每个线程的末尾状态进行最后操作。

在以下例子中,使用ForEach总结多个Order的全部价格。在ForEach方法中,首先把参数早先化为0f,然后用把同一个Order的四个OrderItem价格进行添加,统计出Order的标价,最终把三个Order的标价进行添加,统计出多少个Order的完整价格。

Ajax 36😉

 1     public class Order  2     {  3         public int ID;  4         public float Price;  5     }  6   7     public class OrderItem  8     {  9         public int ID; 10         public string Goods; 11         public int OrderID; 12         public float Price; 13         public int Count; 14     } 15  16     class Program 17     { 18         static void Main(string[] args) 19         { 20             //设置最大线程数 21             ThreadPool.SetMaxThreads(1000, 1000); 22             float totalPrice = 0f; 23             //并行查询 24             var parallelResult = Parallel.ForEach(GetOrderList(), 25                      () => 0f,   //把参数初始值设为0 26                      (order, state, orderPrice) => 27                      { 28                          //计算单个Order的价格 29                          orderPrice = GetOrderItem().Where(item => item.OrderID == order.ID) 30                               .Sum(item => item.Price * item.Count); 31                          order.Price = orderPrice; 32                          ThreadPoolMessage(order); 33                           34                          return orderPrice; 35                      }, 36                     (finallyPrice) => 37                     { 38                         totalPrice += finallyPrice;//计算多个Order的总体价格 39                     } 40                 ); 41              42             while (!parallelResult.IsCompleted) 43                 Console.WriteLine("Doing Work!"); 44  45             Console.WriteLine("Total Price is:" + totalPrice); 46             Console.ReadKey(); 47         } 48         //虚拟数据 49         static IList<Order> GetOrderList() 50         { 51             IList<Order> orderList = new List<Order>(); 52             Order order1 = new Order(); 53             order1.ID = 1; 54             orderList.Add(order1); 55             ............ 56             return orderList; 57         } 58         //虚拟数据 59         static IList<OrderItem> GetOrderItem() 60         { 61             IList<OrderItem> itemList = new List<OrderItem>(); 62  63             OrderItem orderItem1 = new OrderItem(); 64             orderItem1.ID = 1; 65             orderItem1.Goods = "iPhone 4S"; 66             orderItem1.Price = 6700; 67             orderItem1.Count = 2; 68             orderItem1.OrderID = 1; 69             itemList.Add(orderItem1); 70             ........... 71             return itemList; 72         } 73  74         //显示线程池现状 75         static void ThreadPoolMessage(Order order) 76         { 77             int a, b; 78             ThreadPool.GetAvailableThreads(out a, out b); 79             string message = string.Format("OrderID:{0}  OrderPrice:{1}\n" + 80                   "  CurrentThreadId is {2}\n  WorkerThreads is:{3}" + 81                   "  CompletionPortThreads is:{4}\n", 82                   order.ID, order.Price, 83                   Thread.CurrentThread.ManagedThreadId, a.ToString(), b.ToString()); 84  85             Console.WriteLine(message); 86         } 87     }

Ajax 37😉

运作结果

Ajax 38

 

 7.2.2 职务并行

在TPL当中还能利用Parallel.Invoke方法触发七个异步义务,其中 actions
中得以涵盖七个章程仍旧委托,parallelOptions用于配置Parallel类的操作。
public static void Invoke(Action[] actions ) public static void
Invoke(ParallelOptions parallelOptions, Action[] actions )
下边例子中使用了Parallet.Invoke并行查询多少个Person,actions当中可以绑定方法、lambda表达式或者委托,注意绑定方法时必须是再次回到值为void的无参数方法。

Ajax 39😉

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             //设置最大线程数
 6             ThreadPool.SetMaxThreads(1000, 1000);
 7             
 8             //任务并行
 9             Parallel.Invoke(option,
10                 PersonMessage, 
11                 ()=>ThreadPoolMessage(GetPersonList()[1]),  
12                 delegate(){
13                     ThreadPoolMessage(GetPersonList()[2]);
14                 });
15             Console.ReadKey();
16         }
17 
18         static void PersonMessage()
19         {
20             ThreadPoolMessage(GetPersonList()[0]);
21         }
22 
23         //显示线程池现状
24         static void ThreadPoolMessage(Person person)
25         {
26             int a, b;
27             ThreadPool.GetAvailableThreads(out a, out b);
28             string message = string.Format("Person  ID:{0} Name:{1} Age:{2}\n" +
29                   "  CurrentThreadId is {3}\n  WorkerThreads is:{4}" +
30                   "  CompletionPortThreads is :{5}\n",
31                   person.ID, person.Name, person.Age,
32                   Thread.CurrentThread.ManagedThreadId, a.ToString(), b.ToString());
33 
34             Console.WriteLine(message);
35         }
36 
37         //模拟源数据
38         static IList<Person> GetPersonList()
39         {
40             var personList = new List<Person>();
41 
42             var person1 = new Person();
43             person1.ID = 1;
44             person1.Name = "Leslie";
45             person1.Age = 30;
46             personList.Add(person1);
47             ..........
48             return personList;
49         }
50     }

Ajax 40😉

运转结果

Ajax 41

 

 

7.3 Task简介

以Thread创建的线程被默许为前台线程,当然你可以把线程IsBackground属性设置为true,但TPL为此提供了一个更简便的类Task。
Task存在于System.Threading.Tasks命名空间当中,它可以看做异步委托的简易替代品。
通过Task的Factory属性将赶回TaskFactory类,以TaskFactory.StartNew(Action)方法可以创制一个新线程,所开创的线程默许为后台线程。

Ajax 42😉

 1     class Program  2     {  3         static void Main(string[] args)  4         {  5             ThreadPool.SetMaxThreads(1000, 1000);  6             Task.Factory.StartNew(() => ThreadPoolMessage());  7             Console.ReadKey();  8         }  9  10         //显示线程池现状 11         static void ThreadPoolMessage() 12         { 13             int a, b; 14             ThreadPool.GetAvailableThreads(out a, out b); 15             string message = string.Format("CurrentThreadId is:{0}\n" + 16                 "CurrentThread IsBackground:{1}\n" + 17                 "WorkerThreads is:{2}\nCompletionPortThreads is:{3}\n", 18                  Thread.CurrentThread.ManagedThreadId, 19                  Thread.CurrentThread.IsBackground.ToString(), 20                  a.ToString(), b.ToString()); 21             Console.WriteLine(message); 22         } 23     }

Ajax 43😉

运行结果

Ajax 44

 

 

若要裁撤处理,能够选拔CancellationTakenSource对象,在TaskFactory中涵盖有法子
public Task StartNew( Action action, CancellationToken cancellationToken
)
在格局中参与CancellationTakenSource对象的CancellationToken属性,可以操纵职务的运行,调用CancellationTakenSource.Cancel时任务就会自动甘休。上边以图片下载为例子介绍一下TaskFactory的选用。

服务器端页面

Ajax 45😉

 1 <html xmlns="http://www.w3.org/1999/xhtml">
 2 <head runat="server">
 3     <title></title>
 4     <script type="text/C#" runat="server">
 5         private static List<string> url=new List<string>();
 6 
 7         protected void Page_Load(object sender, EventArgs e)
 8         {
 9             if (!Page.IsPostBack)
10             {
11                 url.Clear();
12                 Application["Url"] = null;
13             }
14         }
15 
16         protected void CheckBox_CheckedChanged(object sender, EventArgs e)
17         {
18             CheckBox checkBox = (CheckBox)sender;
19             if (checkBox.Checked)
20                 url.Add(checkBox.Text);
21             else
22                 url.Remove(checkBox.Text);
23             Application["Url"]= url;
24         }
25 </script>
26 </head>
27 <body>
28     <form id="form1" runat="server" >
29     <div align="left">
30        <div align="center" style="float: left;">
31          <asp:Image ID="Image1" runat="server" ImageUrl="~/Images/A.jpg" /><br />
32          <asp:CheckBox ID="CheckBox1" runat="server" AutoPostBack="True" 
33                oncheckedchanged="CheckBox_CheckedChanged" Text="A.jpg" />
34        </div>
35        <div align="center" style="float: left">
36           <asp:Image ID="Image2" runat="server" ImageUrl="~/Images/B.jpg" /><br />
37           <asp:CheckBox ID="CheckBox2" runat="server" AutoPostBack="True" 
38                oncheckedchanged="CheckBox_CheckedChanged" Text="B.jpg" />
39        </div>
40        <div align="center" style="float: left">
41           <asp:Image ID="Image3" runat="server" ImageUrl="~/Images/C.jpg" /><br />
42           <asp:CheckBox ID="CheckBox3" runat="server" AutoPostBack="True" 
43                oncheckedchanged="CheckBox_CheckedChanged" Text="C.jpg" />

相关文章