FM并发编程练习:改写苏飞的C#多线程网站压力测试程序

为了展示如何使用FastMessenger编写并发程序,我会重写或改写一些其它网友的多线程程序,给愿意学习和了解FastMessenger的朋友提供更多直观的范例。一般来说我会对原来的程序进行一些技术分析,得出原作者的设计意图和实现手段,然后解释用FastMessenger来重写或改写会使用哪些设计,最后给出源程序。

这次我选择的是苏飞网友(本人在此表示感谢)的C#多线程网站压力测试程序,他的博客和相关博文地址如下:

苏飞—C#.Net

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的请求。

有了上面对原来的程序和改写的程序的分析,下面列出的源程序,就不再逐行解释了,因为对应关系已经很明细了。

View Code
  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)下载。

如有任何问题,请留言,我会尽量解答。

原文地址:https://www.cnblogs.com/Leo_wl/p/2813281.html