.net2.0实现多线程

 简介

多线程总是那么让人振奋。大家都希望能够同时处理很多事情,不过如果我们没有正确的硬件的话,我们很难达到这点。到目前为止,我们所做的只是分开CPU使用较多的工作,使其为后台进程,这样可以使得界面上不被阻塞。

不过我希望能够得到更好的效果,并充分利用当前最新的多CPU效能。因此,我将写一个真正的多线程实例,将会有多个线程作为后台线程在运行。

这就是这篇文章将要写的,不得不说的是,最终的结果实在是让我很激动。希望你也能够发觉它的用处。
在有4个CPU的多CPU服务器上,我得到了280%的效果(测试的是CPU型的任务),在一些非CPU占用较多的任务中,它可以提高到500% 到1000%的性能。

背景

网上也有不少介绍.Net 2.0下的多线程的文章,应该说,我从它们中受益颇多。我正在使用的是BackgroundWorker .Net 2.0组件(不过也有实现在.net 1.1下的代码)。

这里,我列出一些有用的文章链接:
来自Paul Kimmel的很好的介绍性文章 http://www.informit.com/articles/article.asp?p=459619&seqNum=5&rl=1
来自Juval Löwy的介绍性文章 http://www.devx.com/codemag/Article/20639/1954?pf=true
(必看)Joseph Albahari的C#中使用线程 http://www.albahari.com/threading/part3.html
Michael Weinhardt写的在Windows Forms 2.0中一个简单安全的多线程,我使用了这个网页中的CPU密集型任务,这是他从Chris Sell的文章中引用的。
http://www.mikedub.net/mikeDubSamples/SafeReallySimpleMultithreadingInWindowsForms20/SafeReallySimpleMultithreadingInWindowsForms20.htm
如果你对多线程世界仍然不是特别熟悉或者希望了解最新的.Net 2.0的 BackgroundWorker组件,那么应该好好读读上面的文章。

提出的问题

任何一个任务……无论是CPU密集型还是普通的任务:

CPU密集型:它可以分成一个、两个或多个线程,每个线程会占用一个CPU(这样就使得程序的性能翻番)

普通任务:每一个顺序执行的普通任务,在进行数据存储或使用一个web service的时候都会有一些延迟。所有的这些,都意味着这些没有使用的时间对于用户或任务本身来说有了浪费。这些时间将被重新安排,并将被并行的任务使用,不会再丢失。也就是说,如果有100个100ms延迟的任务,它们在单线程模型和20个线程模型的性能差距会达到1000%。

我们说,如果要处理一个创建一个网站多个块的任务,不是顺序的执行,而是花1-4秒钟把所有的section创建好;商标,在线用户,最新文章,投票工具 等等…… 如果我们能够异步地创建它们,然后在发送给用户,会怎么样?我们就会节省很多webservice的调用,数据库的调用,许多宝贵的时间……这些调用会更 快地执行。看上去是不是很诱人?

解决方案如下:

调用BackgroundWorker,正如我们想要的那样,我们会继承它。后台worker会帮助我们建立一个“Worker”,用于异步地做一个工作。

我们想做的是建立一个工厂Factory(只是为了面向对象的设计,于设计模式无关),任务会放在在这个Factory中执行。这意味着,我们将有一类的任务,一些进程,一些知道如何执行任务的worker。

当然我们需要一个负责分配任务给这些worker的manager,告诉这些worker当它们做完一步或全部时,做什么事情。当然,我们也需要manager能够告诉worker停止当前的任务。它们也需要休息啊:)当manager说停止的时候,它们就应该停止。
我们将会从底至上地解释这些,首先从Worker说起,然后再继续Manager。

Worker

它是Background worker的继承类,我们构建一个构造函数,并分配两个BackgroundWorker的属性,分别是WorkerReportsProgress 和WorkerSupportsCancellation,它们的功能就向其名字的意义一样:报告进度,停止任务。每个Worker还有一个id,Manager将会通过这个id控制它们。

public class MTWorker : BackgroundWorker
   {
      #region Private members
      private int _idxLWorker = 0;
      #endregion

      #region Properties
      public int IdxLWorker
      {
         get { return _idxLWorker; }
         set { _idxLWorker = value; }
      }
      #endregion

      #region Constructor
      public MTWorker()
      {
         WorkerReportsProgress = true;
         WorkerSupportsCancellation = true;
      }
      public MTWorker(int idxWorker)
         : this()
      {
         _idxLWorker = idxWorker;
      }
      #endregion
另外,我们将重载BackgroundWorker的一些函数。事实上,最有意思的是,究竟谁在做真正的工作?它就是OnDoWork,当我们invoke或者启动多线程的时候,它就会被调用。在这里,我们启动任务、执行任务、取消和完成这个任务。
我加了两个可能的任务,一个是普通型的,它会申请并等待文件系统、网络、数据库或Webservices的调用。另一个是CPU密集型的任务:计算PI值。你可以试试增加或减少线程数量后,增加或是减少的延迟(我的意思是增减Worker的数量)

OnDoWork方法的代码

protected override void OnDoWork(DoWorkEventArgs e)

{

   //Here we receive the necessary data for doing the work...

   //we get an int but it could be a struct, class, whatever..

   int digits = (int)e.Argument;

   double tmpProgress = 0;

   int Progress = 0;

   String pi = "3";



   // This method will run on a thread other than the UI thread.

   // Be sure not to manipulate any Windows Forms controls created

   // on the UI thread from this method.

   this.ReportProgress(0, pi);

   //Here we tell the manager that we start the job..



   Boolean bJobFinished = false;

   int percentCompleteCalc = 0;

   String TypeOfProcess = "NORMAL"; //Change to "PI" for a cpu intensive task

   //Initialize calculations

   while (!bJobFinished)

   {

      if (TypeOfProcess == "NORMAL")

      {

         #region Normal Process simulation, putting a time

                 delay to emulate a wait-for-something

         while (!bJobFinished)

         {

            if (CancellationPending)

            {

               e.Cancel = true;

               return; //break

            }

            //Perform another calculation step

            Thread.Sleep(250);

            percentCompleteCalc = percentCompleteCalc + 10;

            if (percentCompleteCalc >= 100)

               bJobFinished = true;

            else

               ReportProgress(percentCompleteCalc, pi);

         }

         #endregion

      }

      else

      {

         #region Pi Calculation - CPU intensive job,

                  beware of it if not using threading ;) !!

         //PI Calculation

         if (digits > 0)

         {

            pi += ".";

            for (int i = 0; i < digits; i += 9)

            {

               // Work out pi. Scientific bit :-)

               int nineDigits = NineDigitsOfPi.StartingAt(i + 1);

               int digitCount = System.Math.Min(digits - i, 9);

               string ds = System.String.Format("{0:D9}", nineDigits);

               pi += ds.Substring(0, digitCount);



               // Show progress

               tmpProgress = (i + digitCount);

               tmpProgress = (tmpProgress / digits);

               tmpProgress = tmpProgress * 100;

               Progress = Convert.ToInt32(tmpProgress);

               ReportProgress(Progress, pi);

               // Deal with possible cancellation

               if (CancellationPending) //If the manager says to stop, do so..

               {

                  bJobFinished = true;

                  e.Cancel = true;

                  return;

               }

            }

         }

         bJobFinished = true;

         #endregion

      }



   }

   ReportProgress(100, pi); //Last job report to the manager ;)

   e.Result = pi; //Here we pass the final result of the Job

}

Manager

这是一个很有趣的地方,我确信它有很大的改进空间-欢迎任何的评论和改进!它所做的是给每个线程生成和配置一个Worker,然后给这些 Worker安排任务。目前,传给Worker的参数是数字,但是它能够传送一个包含任务定义的类或结构。一个可能的改进是选择如何做这些内部工作的策略模式。

调用InitManager方法配置任务,和它的数量等属性。然后,创建一个多线程Worker的数组,配置它们。
配置的代码如下:

private void ConfigureWorker(MTWorker MTW)

{

//We associate the events of the worker

MTW.ProgressChanged += MTWorker_ProgressChanged;

MTW.RunWorkerCompleted += MTWorker_RunWorkerCompleted;

}


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.
最主要的方法是AssignWorkers,它会检查所有的Worker,如果发现没有任务的Worker,就分配一个任务给它。直到扫描一遍之后,没有发现任何有任务的Worker,这样就意味这任务结束了。不需要再做别的了!

代码如下:

public void AssignWorkers()

{

   Boolean ThereAreWorkersWorking = false;

   //We check all workers that are not doing a job... and assign a new one

   foreach (MTWorker W in _arrLWorker)

   {

      if (W.IsBusy == false)

      {

         //If there are still jobs to be done...

         //we assign the job to the free worker

         if (_iNumJobs > _LastSentThread)

         {

          //We control the threads associated to a worker

          //(not meaning the jobs done) just 4 control.

          _LastSentThread = _LastSentThread + 1;

          W.JobId = _LastSentThread; //We assign the job number..

          W.RunWorkerAsync(_iPiNumbers); //We pass the parameters for the job.

          ThereAreWorkersWorking = true;

          //We have at least this worker we just assigned the job working..

         }

      }

      else

      {

         ThereAreWorkersWorking = true;

      }

   }



   if (ThereAreWorkersWorking == false)

   {

      //This means that no worker is working and no job has been assigned.

      //this means that the full package of jobs has finished

      //We could do something here...

      Button BtnStart = (Button)FormManager.Controls["btnStart"];

      Button BtnCancel = (Button)FormManager.Controls["btnCancel"];

      BtnStart.Enabled = true;

      BtnCancel.Enabled = false;

      MessageBox.Show("Hi, I'm the manager to the boss (user): " +

                      "All Jobs have finished, boss!!");

   }

}


只要有任务完成,这个方法就会被调用。从而,保证所有的任务能够完成。

我们还通过一个属性链接到Form上,这样我们就能向UI上输出我们想要的任何消息了。当然,你可能想链接到其它的一些类,不过这是最基本最通用的。

Well… improving it we could get a BackgroundManager for all our application needs..


界面

连接到界面上,并不是最主要的功能。这一部分的代码量非常少,也很简单:在Manager中添加一个引用,并在form的构造函数中配置它。
在一个按钮中,执行Manager类的LaunchManagedProcess方法。

private MTManager LM;

public Form1()

{

   InitializeComponent();

   LM = new MTManager(this, 25);

   LM.InitManager();

}



private void btnStart_Click(object sender, EventArgs e)

{

   btnStart.Enabled = false;

   btnCancel.Enabled = true;

   LM.LaunchManagedProcess();

}



private void btnCancel_Click(object sender, EventArgs e)

{

   LM.StopManagedProcess();

   btnCancel.Enabled = false;

   btnStart.Enabled = true;

}

原文地址:http://www.codeproject.com/KB/threads/RealMultiThreading.aspx

源码下载:http://www.codeproject.com/KB/threads/RealMultiThreading/RealMultiThreading_src.zip

你可能感兴趣的:(ASP.NET)