为了展示如何使用FastMessenger编写并发程序,我会重写或改写一些其它网友的多线程程序,给愿意学习和了解FastMessenger的朋友提供更多直观的范例。一般来说我会对原来的程序进行一些技术分析,得出原作者的设计意图和实现手段,然后解释用FastMessenger来重写或改写会使用哪些设计,最后给出源程序。
这次我选择的是苏飞网友(本人在此表示感谢)的C#多线程网站压力测试程序,他的博客和相关博文地址如下:
C#多线程|匿名委托传参数|测试您的网站能承受的压力|附源代码
C#多线程|匿名委托传参数|测试您的网站能承受的压力|附源代码--升级版
据他自己介绍,这个程序虽然有一些实用价值,但主要是作为一个多线程的演示程序来写的。请大家在继续往下阅读本文之前,先到上面的地址看看他的文章和源程序,因为我接着的分析是基于他的程序来展开的。
这个图是我对苏飞的程序的分析和归纳的结果,如有错误,请指正。这是一个GUI(Windows Form)程序,程序启动后并不开始测试,而是等用户输入一些参数并按了一个button后开始。在button的event函数里,存放了创建和启动新线程的主要逻辑。每个线程都对应表中的一行,并且每个线程在创建的时候就通过一个匿名函数获得了和该行对应的行号和其它完成网站测试所需要的所有参数。这样每个线程都能提供正确的行号和列名,通过UpDateDgv()函数来更新GUI。
除了参数不同外,所有线程的主体逻辑都是一样的,都是调用PingTask()。但PingTask()里的更新GUI的代码却不是由这些新创建的线程直接调用的,而是通过调用BeginInvoke来间接调用的。这属于Windows Form的特性,就不多解释了。
上面所有的代码都被写在同一个class TextFor里了,但却没有race condition。主要是因为测试逻辑本身就是独立的,互相直接没有要协调同步的地方。然后所有的参数都被封装在PingTask()里了,对外没有要求,也没有接受外部请求(比如立即停止)的需求。最后再加上线程和GUI之间只有简单数据(行号、列名、和string的值)的单向联系(或耦合),以及BeginInvoke又隔离了一次,所以基本没有什么线程安全的问题要考虑。
分析完毕,现在转到如何用FastMessenger来编程了。FastMessenger里的object叫做active object,就是活的object的意思,与SOA里的service类似,你一给个request或message,它就会执行相应的功能,所以叫做活的、独立的object。等一下看代码就知道了,和其它的object没有什么区别,你可以把它理解成“封装了线程的object”。对active object最直观的理解方法是把它拟人化,比如下图中的兰色和红色的小人图像。当你给一个人(比如你的同事)布置了一个任务后,他自己有手有脚有脑子,自己会完成这个任务。你不需要象上面的多线程程序那样,即作爹(建立线程)又作妈(安排执行PingTask()函数),出了事还得善后(exception handling)。
所以我设计的时候,安排了一个角色叫SiteTester(图中下面那排兰色小人)。根据原来程序的结构,每个SiteTester负责表中的一行。由button的event函数负责建立(一人建立一次就可以了)SiteTester,并给每个SiteTester发测试请求,和原来的程序一样,每个请求里包含了测试所需要的所有参数。然后在表的那边(图中上部)安排了一个角色叫ViewUpdater,就一个人,负责接收所有SiteTester的更新GUI的请求。
有了上面对原来的程序和改写的程序的分析,下面列出的源程序,就不再逐行解释了,因为对应关系已经很明细了。
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel; 4 using System.Data; 5 using System.Drawing; 6 using System.Text; 7 using System.Windows.Forms; 8 using System.Net; 9 using System.Threading; 10 using System.Data.SqlClient; 11 using Com.FastMessenger; 12 using Com.FastMessenger.Impl; 13 14 namespace AutoFor 15 { 16 public partial class TextFor : Form 17 { 18 private delegate void UpDateDgvDelegate(string msg, int rowId, string columnName); 19 private UpDateDgvDelegate _upDateStateDelegate; 20 private IMessenger messenger; 21 private int lastThreadCount = 0; 22 23 public TextFor() 24 { 25 InitializeComponent(); 26 _upDateStateDelegate = new UpDateDgvDelegate(UpDateDgv); 27 28 messenger = new Messenger(); 29 messenger.RegisterReceiver(this, "ViewUpdater", "WebTestCompleted"); 30 } 31 32 private void UpDateDgv(string msg, int rowId, string columnName) 33 { 34 try 35 { 36 dgvTextFor.Rows[rowId].Cells[columnName].Value = msg.ToString(); 37 } 38 catch { } 39 } 40 41 public void WebTestCompleted(string msg, int rowId, string columnName) 42 { 43 this.BeginInvoke(_upDateStateDelegate, msg, rowId, columnName); 44 } 45 46 private void CreateTable(int rows) 47 { 48 DataTable dt_Sale = new DataTable(); 49 DataColumn dc = null; 50 //线程ID 51 dc = new DataColumn(); 52 dc.ColumnName = "线程ID"; 53 dc.DefaultValue = "1"; 54 dc.DataType = Type.GetType("System.String"); 55 dt_Sale.Columns.Add(dc); 56 57 //循环类型 58 dc = new DataColumn(); 59 dc.ColumnName = "循环类型"; 60 dc.DefaultValue = " "; 61 dc.DataType = Type.GetType("System.String"); 62 dt_Sale.Columns.Add(dc); 63 64 //当前循环次数 65 dc = new DataColumn(); 66 dc.ColumnName = "当前循环次数"; 67 dc.DefaultValue = " "; 68 dc.DataType = Type.GetType(" System.String"); 69 dt_Sale.Columns.Add(dc); 70 71 //开始时间 72 dc = new DataColumn(); 73 dc.ColumnName = "开始时间"; 74 dc.DefaultValue = " "; 75 dc.DataType = Type.GetType("System.String"); 76 dt_Sale.Columns.Add(dc); 77 78 //结束时间 79 dc = new DataColumn(); 80 dc.ColumnName = "结束时间"; 81 dc.DefaultValue = " "; 82 dc.DataType = Type.GetType("System.String"); 83 dt_Sale.Columns.Add(dc); 84 85 //总用时(毫秒) 86 dc = new DataColumn(); 87 dc.ColumnName = "总用时(毫秒)"; 88 dc.DefaultValue = " "; 89 dc.DataType = Type.GetType("System.String"); 90 dt_Sale.Columns.Add(dc); 91 92 //测试数据 93 dc = new DataColumn(); 94 dc.ColumnName = "数据"; 95 dc.DefaultValue = " "; 96 dc.DataType = Type.GetType("System.String"); 97 dt_Sale.Columns.Add(dc); 98 99 DataRow dr = dt_Sale.NewRow(); 100 for (int i = 1; i < rows; i++) 101 { 102 dr["线程ID"] = i.ToString(); 103 dr["循环类型"] = "For循环"; 104 dr["当前循环次数"] = "0"; 105 dr["开始时间"] = "00:00:00"; 106 dr["结束时间"] = "00:00:00"; 107 dr["总用时(毫秒)"] = "0"; 108 dr["数据"] = ""; 109 dt_Sale.Rows.Add(dr); 110 dr = dt_Sale.NewRow(); 111 } 112 dgvTextFor.DataSource = dt_Sale; 113 } 114 115 private void button3_Click(object sender, EventArgs e) 116 { 117 int threadCount = Convert.ToInt32(txtCount.Text.Trim()); 118 int repeatCount = Convert.ToInt32(txtNumber.Text.Trim()); 119 string url = textBox1.Text.Trim(); 120 int repeatInterval = Convert.ToInt32(txtForTime.Text.Trim()); 121 122 CreateTable(threadCount + 1); 123 124 // when lastThreadCount > threadCount 125 for (int i = threadCount; i < lastThreadCount; i++) 126 { 127 messenger.DeregisterReceiver("SiteTester-" + i); 128 } 129 130 // when lastThreadCount < threadCount 131 for (int i = lastThreadCount; i < threadCount; i++) 132 { 133 SiteTester tester = new SiteTester(messenger, "ViewUpdater", "WebTestCompleted"); 134 messenger.RegisterReceiver(tester, "SiteTester-" + i, "TestSite"); 135 } 136 137 for (int i = 0; i < threadCount; i++) 138 { 139 messenger.SendMessage("SiteTester-" + i, "TestSite", i, repeatCount, url, repeatInterval); 140 } 141 142 lastThreadCount = threadCount; 143 } 144 } 145 146 class SiteTester 147 { 148 private IMessenger messenger; 149 private string returnId, returnPort; 150 151 public SiteTester(IMessenger messenger, string returnId, string returnPort) 152 { 153 this.messenger = messenger; 154 this.returnId = returnId; 155 this.returnPort = returnPort; 156 } 157 158 public void TestSite(int dgvrowid, int number, string url, int time) 159 { 160 DateTime st = DateTime.Now; 161 162 messenger.SendMessage(returnId, returnPort, st.ToString("hh-mm-ss"), dgvrowid, "开始时间"); 163 164 for (int i = 0; i < number; i++) 165 { 166 try 167 { 168 DateTime stThread = DateTime.Now; 169 170 HttpHelps hh = new HttpHelps(); 171 172 string strdate = hh.GetHttpRequestStringByNUll_Get(url, null); 173 DateTime et = DateTime.Now; 174 175 messenger.SendMessage(returnId, returnPort, strdate, dgvrowid, "数据"); 176 messenger.SendMessage(returnId, returnPort, (i + 1).ToString(), dgvrowid, "当前循环次数"); 177 messenger.SendMessage(returnId, returnPort, et.ToString("hh-mm-ss"), dgvrowid, "结束时间"); 178 messenger.SendMessage(returnId, returnPort, ExecDateDiff(st, et), dgvrowid, "总用时(毫秒)"); 179 180 while (stThread.AddSeconds(time) > DateTime.Now) 181 { 182 Thread.Sleep(200); 183 } 184 } 185 catch { } 186 } 187 } 188 189 private string ExecDateDiff(DateTime dateBegin, DateTime dateEnd) 190 { 191 TimeSpan ts1 = new TimeSpan(dateBegin.Ticks); 192 TimeSpan ts2 = new TimeSpan(dateEnd.Ticks); 193 TimeSpan ts3 = ts1.Subtract(ts2).Duration(); 194 return ts3.TotalMilliseconds.ToString(); 195 } 196 } 197 }
为了节省版面,上面的源程序已经去掉了一些原来的过于明显的注释了。要运行的话,还需要FastMessenger的代码(要看了本文之后下载的,因为刚改了个不bug),请到这里(https://github.com/fastmessenger/RI-in-Csharp)下载。
如有任何问题,请留言,我会尽量解答。