factory threads

 

  1 .NET 2.0中真正的多线程实例
  2 Real Multi-threading in .NET 2.0
  3 remex1980 翻译于 2007-5-16 20:43:21 
  4 原作者: Jose Luis Latorre
  5 原文地址:http://www.codeproject.com/useritems/RealMultiThreading.asp
  6 
  7 
  8 多线程实例源代码(保留原文处链接)
  9 http://www.codeproject.com/useritems/RealMultiThreading/RealMultiThreading_src.zip
 10 
 11 
 12  
 13 
 14 
 15 简介
 16 
 17 多线程总是那么让人振奋。大家都希望能够同时处理很多事情,不过如果我们没有正确的硬件的话,我们很难达到这点。到目前为止,我们所做的只是分开CPU使用较多的工作,使其为后台进程,这样可以使得界面上不被阻塞。
 18 
 19 不过我希望能够得到更好的效果,并充分利用当前最新的多CPU效能。因此,我将写一个真正的多线程实例,将会有多个线程作为后台线程在运行。
 20 
 21 这就是这篇文章将要写的,不得不说的是,最终的结果实在是让我很激动。希望你也能够发觉它的用处。
 22 
 23 在有4个CPU的多CPU服务器上,我得到了280%的效果(测试的是CPU型的任务),在一些非CPU占用较多的任务中,它可以提高到500% 到1000%的性能。
 24 
 25 背景
 26 
 27 网上也有不少介绍.Net 2.0下的多线程的文章,应该说,我从它们中受益颇多。我正在使用的是BackgroundWorker .Net 2.0组件(不过也有实现在.net 1.1下的代码)。
 28 
 29 这里,我列出一些有用的文章链接:
 30 来自Paul Kimmel的很好的介绍性文章 http://www.informit.com/articles/article.asp?p=459619&seqNum=5&rl=1
 31 来自Juval Löwy的介绍性文章 http://www.devx.com/codemag/Article/20639/1954?pf=true
 32 (必看)Joseph Albahari的C#中使用线程 http://www.albahari.com/threading/part3.html
 33 Michael Weinhardt写的在Windows Forms 2.0中一个简单安全的多线程,我使用了这个网页中的CPU密集型任务,这是他从Chris Sell的文章中引用的。
 34 http://www.mikedub.net/mikeDubSamples/SafeReallySimpleMultithreadingInWindowsForms20/SafeReallySimpleMultithreadingInWindowsForms20.htm
 35 
 36 如果你对多线程世界仍然不是特别熟悉或者希望了解最新的.Net 2.0的 BackgroundWorker组件,那么应该好好读读上面的文章。
 37 
 38 
 39 
 40 提出的问题
 41 
 42 任何一个任务……无论是CPU密集型还是普通的任务:
 43 
 44 CPU密集型:它可以分成一个、两个或多个线程,每个线程会占用一个CPU(这样就使得程序的性能翻番)
 45 
 46 普通任务:每一个顺序执行的普通任务,在进行数据存储或使用一个web service的时候都会有一些延迟。所有的这些,都意味着这些没有使用的时间对于用户或任务本身来说有了浪费。这些时间将被重新安排,并将被并行的任务使用,不会再丢失。也就是说,如果有100个100ms延迟的任务,它们在单线程模型和20个线程模型的性能差距会达到1000%。
 47 
 48 
 49 我们说,如果要处理一个创建一个网站多个块的任务,不是顺序的执行,而是花1-4秒钟把所有的section创建好;商标,在线用户,最新文章,投票工具等等…… 如果我们能够异步地创建它们,然后在发送给用户,会怎么样?我们就会节省很多webservice的调用,数据库的调用,许多宝贵的时间……这些调用会更快地执行。看上去是不是很诱人?
 50 
 51 
 52 
 53 解决方案如下:
 54 
 55 调用BackgroundWorker,正如我们想要的那样,我们会继承它。后台worker会帮助我们建立一个“Worker”,用于异步地做一个工作。
 56 
 57 我们想做的是建立一个工厂Factory(只是为了面向对象的设计,于设计模式无关),任务会放在在这个Factory中执行。这意味着,我们将有一类的任务,一些进程,一些知道如何执行任务的worker。
 58 
 59 当然我们需要一个负责分配任务给这些worker的manager,告诉这些worker当它们做完一步或全部时,做什么事情。当然,我们也需要manager能够告诉worker停止当前的任务。它们也需要休息啊:)当manager说停止的时候,它们就应该停止。
 60 
 61 我们将会从底至上地解释这些,首先从Worker说起,然后再继续Manager。
 62 
 63 Worker
 64 
 65 
 66 它是Background worker的继承类,我们构建一个构造函数,并分配两个BackgroundWorker的属性,分别是WorkerReportsProgress 和WorkerSupportsCancellation,它们的功能就向其名字的意义一样:报告进度,停止任务。每个Worker还有一个id,Manager将会通过这个id控制它们。
 67 
 68 
 69 public class MTWorker : BackgroundWorker
 70    {
 71       #region Private members
 72       private int _idxLWorker = 0;
 73       #endregion
 74 
 75       #region Properties
 76       public int IdxLWorker
 77       {
 78          get { return _idxLWorker; }
 79          set { _idxLWorker = value; }
 80       }
 81       #endregion
 82 
 83       #region Constructor
 84       public MTWorker()
 85       {
 86          WorkerReportsProgress = true;
 87          WorkerSupportsCancellation = true;
 88       }
 89       public MTWorker(int idxWorker)
 90          : this()
 91       {
 92          _idxLWorker = idxWorker;
 93       }
 94       #endregion
 95 
 96 
 97 另外,我们将重载BackgroundWorker的一些函数。事实上,最有意思的是,究竟谁在做真正的工作?它就是OnDoWork,当我们invoke或者启动多线程的时候,它就会被调用。在这里,我们启动任务、执行任务、取消和完成这个任务。
 98 我加了两个可能的任务,一个是普通型的,它会申请并等待文件系统、网络、数据库或Webservices的调用。另一个是CPU密集型的任务:计算PI值。你可以试试增加或减少线程数量后,增加或是减少的延迟(我的意思是增减Worker的数量)
 99 
100 
101 
102 OnDoWork方法的代码
103 
104 
105 protected override void OnDoWork(DoWorkEventArgs e)
106 {
107    //Here we receive the necessary data for doing the work 
108    //we get an int but it could be a struct, class, whatever..
109    int digits = (int)e.Argument;
110    double tmpProgress = 0;
111    int Progress = 0;
112    String pi = "3";
113 
114    // This method will run on a thread other than the UI thread.
115    // Be sure not to manipulate any Windows Forms controls created
116    // on the UI thread from this method.
117    this.ReportProgress(0, pi);
118    //Here we tell the manager that we start the job..
119 
120    Boolean bJobFinished = false;
121    int percentCompleteCalc = 0;
122    String TypeOfProcess = "NORMAL"//Change to "PI" for a cpu intensive task
123    //Initialize calculations
124    while (!bJobFinished)
125    {
126       if (TypeOfProcess == "NORMAL")
127       {
128          #region Normal Process simulation, putting a time 
129                  delay to emulate a wait-for-something
130          while (!bJobFinished)
131          {
132             if (CancellationPending)
133             {
134                e.Cancel = true;
135                return//break
136             }
137             //Perform another calculation step
138             Thread.Sleep(250);
139             percentCompleteCalc = percentCompleteCalc + 10;
140             if (percentCompleteCalc >= 100)
141                bJobFinished = true;
142             else
143                ReportProgress(percentCompleteCalc, pi);
144          }
145          #endregion
146       }
147       else
148       {
149          #region  Pi Calculation - CPU intensive job, 
150                   beware of it if not using threading ;) !!
151          //PI Calculation
152          if (digits > 0)
153          {
154             pi += ".";
155             for (int i = 0; i < digits; i += 9)
156             {
157                // Work out pi. Scientific bit :-)
158                int nineDigits = NineDigitsOfPi.StartingAt(i + 1);
159                int digitCount = System.Math.Min(digits - i, 9);
160                string ds = System.String.Format("{0:D9}", nineDigits);
161                pi += ds.Substring(0, digitCount);
162 
163                // Show progress
164                tmpProgress = (i + digitCount);
165                tmpProgress = (tmpProgress / digits);
166                tmpProgress = tmpProgress * 100;
167                Progress = Convert.ToInt32(tmpProgress);
168                ReportProgress(Progress, pi);
169                // Deal with possible cancellation
170                if (CancellationPending) //If the manager says to stop, do so..
171                {
172                   bJobFinished = true;
173                   e.Cancel = true;
174                   return;
175                }
176             }
177          }
178          bJobFinished = true;
179          #endregion
180       }
181 
182    }
183    ReportProgress(100, pi); //Last job report to the manager ;)
184    e.Result = pi; //Here we pass the final result of the Job
185 }
186 Manager
187 
188 这是一个很有趣的地方,我确信它有很大的改进空间-欢迎任何的评论和改进!它所做的是给每个线程生成和配置一个Worker,然后给这些Worker安排任务。目前,传给Worker的参数是数字,但是它能够传送一个包含任务定义的类或结构。一个可能的改进是选择如何做这些内部工作的策略模式。
189 
190 调用InitManager方法配置任务,和它的数量等属性。然后,创建一个多线程Worker的数组,配置它们。
191 配置的代码如下:
192 
193 
194 private void ConfigureWorker(MTWorker MTW)
195 {
196   //We associate the events of the worker
197   MTW.ProgressChanged += MTWorker_ProgressChanged; 
198   MTW.RunWorkerCompleted += MTWorker_RunWorkerCompleted; 
199 }
200 
201 Like this, the Worker’s subclassed thread management Methods are linked to the Methods held by the Manager. Note that with a Strategy pattern implemented we could assign these to the proper manager for these methods.
202 最主要的方法是AssignWorkers,它会检查所有的Worker,如果发现没有任务的Worker,就分配一个任务给它。直到扫描一遍之后,没有发现任何有任务的Worker,这样就意味这任务结束了。不需要再做别的了!
203 
204 代码如下:
205 
206 public void AssignWorkers()
207 {
208    Boolean ThereAreWorkersWorking = false;
209    //We check all workers that are not doing a job and assign a new one
210    foreach (MTWorker W in _arrLWorker)
211    {
212       if (W.IsBusy == false)
213       {
214          //If there are still jobs to be done 
215          //we assign the job to the free worker
216          if (_iNumJobs > _LastSentThread)
217          {
218           //We control the threads associated to a worker 
219           //(not meaning the jobs done) just 4 control.
220           _LastSentThread = _LastSentThread + 1;
221           W.JobId = _LastSentThread; //We assign the job number..
222           W.RunWorkerAsync(_iPiNumbers); //We pass the parameters for the job.
223           ThereAreWorkersWorking = true;
224           //We have at least this worker we just assigned the job working..
225          }
226       }
227       else
228       {
229          ThereAreWorkersWorking = true;
230       }
231    }
232 
233    if (ThereAreWorkersWorking == false)
234    { 
235       //This means that no worker is working and no job has been assigned. 
236       //this means that the full package of jobs has finished
237       //We could do something here
238       Button BtnStart = (Button)FormManager.Controls["btnStart"];
239       Button BtnCancel = (Button)FormManager.Controls["btnCancel"];
240       BtnStart.Enabled = true;
241       BtnCancel.Enabled = false;
242       MessageBox.Show("Hi, I'm the manager to the boss (user): " + 
243                       "All Jobs have finished, boss!!");
244    }
245 }
246 
247 只要有任务完成,这个方法就会被调用。从而,保证所有的任务能够完成。
248 
249 我们还通过一个属性链接到Form上,这样我们就能向UI上输出我们想要的任何消息了。当然,你可能想链接到其它的一些类,不过这是最基本最通用的。
250 
251 Well… improving it we could get a BackgroundManager for all our application needs..
252 
253 
254 
255 界面
256 
257 连接到界面上,并不是最主要的功能。这一部分的代码量非常少,也很简单:在Manager中添加一个引用,并在form的构造函数中配置它。
258 在一个按钮中,执行Manager类的LaunchManagedProcess方法。
259 
260 
261 private MTManager LM;
262 public Form1()
263 {
264    InitializeComponent();
265    LM = new MTManager(this25);
266    LM.InitManager();
267 }
268 
269 private void btnStart_Click(object sender, EventArgs e)
270 {
271    btnStart.Enabled = false;
272    btnCancel.Enabled = true;
273    LM.LaunchManagedProcess();
274 }
275 
276 private void btnCancel_Click(object sender, EventArgs e)
277 {
278    LM.StopManagedProcess();
279    btnCancel.Enabled = false;
280    btnStart.Enabled = true;
281 }
282 
283 (下面的自己看喽:)
284 Trying it! 
285 This is the funniest part, changing the properties of how many threads to run simultaneously and how many Jobs to be processed and then try it on different CPU’s… ah, and of course, change the calculation method from a CPU-intensive task to a normal task with a operation delay
286 I would love to know your results and what have you done with this, any feedback would be great!!
287 
288 Exercises For You…
289 This is not done! It could be a MultiThreadJob Framework if there is being done the following: 
290 
291 Implement a Strategy pattern that determines the kind of Worker to produce (with a factory pattern) so we will be able to do different kind of jobs inside the same factory.. what about migrating a database and processing each table in a different way… or integrating systems with this engine… 
292 Implement -or extend- the strategy pattern for determining the treatment for the Input data and the result data of the jobs. We could too set-up a factory for getting the classes into a operating environment. 
293 Optimize the AssignWorkers engine – I am pretty sure it can be improved. 
294 Improve the WorkerManager class in order to be able to attach it to another class instead to only a form. 
295 Send me the code! I Would love to hear from you and what have you done. 
296 
297 About Jose Luis Latorre
298 
299  Professional developer since 1991, having developed on multiple systems and languages since then, from Unix, as400, lotus notes, flash, javascript, asp, prolog, vb, c++, vb.Net, C#
300 
301 Now I'm focused on .Net development, both windows and web with two-three year experience on both and fully up-to date with 2.0 .Net in both Vb and C#
302 Also have experience with SQL server 2005 and Business Intelligence.
303 
304 Jose Luis Lives in Barcelona, Spain, with his cat Pancho. To contact Jose Luis, email him at
305 joslat@gmail.com.
306 
307 
308 Click here to view Jose Luis Latorre's online profile.
309 
posted @ 2007-07-24 16:13  hq5460  阅读(505)  评论(0编辑  收藏  举报