C#中的多线程编程

 

C#.Net平台的通用开发工具,它能建造所有的.Net用。在.Net中所有线程都运行在用程序域(AppDomain)中,许让你想到Win32程,实际上它们还是有很大的不同。用程序域提供了一安全而通用的元,公共言运行可使用它来隔离用程序。注意在.Net用程序的隔离是用程序域而不是程,在程中可以存在几个用程序域,而且线程可以跨越用程序域的范,某个线程中的方法可以用另一个线程的方法,这样就不会造成间调用或等方面的开销。可以说应用程序域是物理程(也即win32中的Process)内的逻辑进程。
     
Visul C#System.Threading 命名空提供一些使得可以行多线程的和接口,其中线程的建有以下三方法:ThreadThreadPoolTimer。下面我就它的使用方法逐个作一简单
1 Thread
       
是最复杂的方法,但它提供了对线程的各灵活控制。首先你必使用它的构造函数建一个线例,它的参数比较简单,只有一个ThreadStart 委托:
[C#]
                   public Thread(ThreadStart start);
然后Start()启它,当然你可以利用它的Priority属性来置或得它的运行enum ThreadPriority  Normal Lowest Highest BelowNormal 
AboveNormal
)。下例:它首先生成了两个线t1t2,然后分别设置它,接着启线程(两线程基本一,只不们输出不一t1“1”t2“2”,根据它各自出字符个数比可大致看出它占用CPU时间之比,也反映出了它各自的)。
 
static void Main(string[] args)
  {
   Thread t1 = new Thread(new ThreadStart(Thread1));
   Thread t2 = new Thread(new ThreadStart(Thread2));

   t1.Priority = ThreadPriority.BelowNormal  ;
   t2.Priority = ThreadPriority.Lowest ;
           t1.Start();
      t2.Start();
   }
  public static void Thread1()
  { 
   for (int i = 1; i < 1000; i++) 
   {//
运行一个循就写一个“1”
     dosth();
    Console.Write("1");
   }
   }
  public static void Thread2()
  { 
   for (int i = 0; i < 1000; i++) 
   {//
运行一个循就写一个“2”
    dosth();
    Console.Write("2");
   }
  }
  public static void dosth()
  {//
用来模拟复杂运算
   for (int j = 0; j < 10000000; j++) 
   {    
    int a=15;
    a = a*a*a*a;
   }
  }
以上程序运行

1111111111111111111111111111111111111111112
1111111111111111111111111111111111111111112

1111111111111111111111111111111111111111112
1111111111111111111111111111111111111111112

1111111111111111111111111111111111111111112
1111111111111111111111111111111111111111112
 
      
从以上果我可以看出,t1线程所占用CPU时间远t2的多,是因t1t2的高,若我t1t2设为Normal,那果是如何?它所占用的CPU时间会一样吗?是的,正如你所料, 

1212112212121212121212121212121212121212121212121212121212
12121212121

21212121212121212121212121212121212121212121212121212121212
1212121212

121212121212121212

 
         
从上例我可看出,它的构造似于win32的工作线程,但更加简单,只需把线程要用的函数作委托,然后把委托作参数构造线例即可。Start()后,便会用相的函数,从那函数第一行行。
 
接下来我们结线程的ThreadState属性来了解线程的控制。ThreadState是一个枚举类型,它反映的是线程所的状。当一个Thread刚创,它的ThreadStateUnstarted;当此线程被Start()之后,它的ThreadState Running;  在此线程启之后,如果想停(阻塞),可以Thread.Sleep() 方法,它有两个重方法(Sleep(int )Sleep(Timespan )),只不是表示时间量的格式不同而已,当在某线程内用此函数,它表示此线程将阻塞一段时间时间是由传递给 Sleep 的毫秒数或Timespan决定的,但若参数0表示挂起此线程以使其它线程能够执行,指定 Infinite 以无限期阻塞线程),此它的ThreadState变为WaitSleepJoin,另外得注意一点的是Sleep()函数被定义为static也意味着它不能和某个线合起来用,也即不存在似于t1.Sleep(10)用!正是如此,Sleep()函数只能由需“Sleep”线程自己用,不允其它线用,正如when to Sleep是个人私事不能由它人决定。但是当某线WaitSleepJoin而又不得不醒它,可使用Thread.Interrupt 方法 ,它将在线程上引ThreadInterruptedException,下面我先看一个例子(注意Sleep用方法):
static void Main(string[] args)
  { 
   Thread t1 = new Thread(new ThreadStart(Thread1));
    t1.Start();
      t1.Interrupt ();
     E.WaitOne ();
     t1.Interrupt ();
          t1.Join();
          Console.WriteLine(“t1 is end”);
  }
  static AutoResetEvent E = new AutoResetEvent(false);
  public static void Thread1()
  {   
   try
   {//
从参数可看出将致休眠
    Thread.Sleep(Timeout.Infinite); 
   }
   catch(System.Threading.ThreadInterruptedException e)
   {//
中断理程序
    Console.WriteLine (" 1st interrupt");
   }
     E.Set ();
   try
   {// 
休眠
    Thread.Sleep(Timeout.Infinite ); 
   }
   catch(System.Threading.ThreadInterruptedException e)
   {
     Console.WriteLine (" 2nd interrupt");
   }//
10
           Thread.Sleep (10000); 
        }
 
运行 1st interrupt
                2nd interrupt
         (10s
)t1 is end
      
从上例我可以看出Thread.Interrupt方法可以把程序从某个阻塞(WaitSleepJoin)状态唤对应的中断理程序,然后继续往下行(它的ThreadState变为Running),此函数的使用必注意以下几点:
1 .
此方法不醒由Sleep致的阻塞,而且一切可线WaitSleepJoin的方法(WaitJoin)都有效。如上例所示使用要把线程阻塞的方法放入try内, 并把相的中断理程序放入catch内。
2 .
某一线Interrupt, 如它正WaitSleepJoin则进入相的中断理程序行, 若此它不WaitSleepJoin 它后来入此状态时 将被立即中断。若在中断前用几次Interrupt 只有第一次用有效正是上例我用同的原因, 这样才能确保第二次Interrupt在第一个中断后用,否可能致第二次用无效(若它在第一个中断前用)。你可以把同试试,其果很可能是:   1st interrupt
       
上例用了另外两个使线WaitSleepJoin的方法:利用同步对象和Thread.Join方法。Join方法的使用比较简单,它表示在用此方法的当前线程阻塞直至另一线程(此例中是t1止或者经过了指定的时间为(若它还带时间量参数),当两个条件(若有)任一出,它立即WaitSleepJoin态进Running(可根据.Join方法的返回判断条件,true线;false时间)
线程的可用Thread.Suspend方法,当某线Running态时对Suspend方法,它将SuspendRequested,但它并不会被立即挂起,直到线程到达安全点之后它才可以将该线程挂起,此它将Suspended。如一个已Suspended线无效,要恢运行只需Thread.Resume即可。
         
最后我们谈的是线程的销毁,我可以销毁线Abort方法,它会在此线程上引ThreadAbortException可把线程内的一些代放入try内,并把相应处理代放入相catch内,当线程正try内代码时如被Abort,它便会跳入相catch行,行完catch快内的代后它将止(若catch行了ResetAbort不同了:它将取消当前Abort求,继续向下行。所以如要确保某线止的最好用Join,如上例)。
2
 ThreadPool
线程池(ThreadPool)是一对较简单的方法,它适于一些需要多个线程而又短任(如一些常于阻塞状线程) ,它的缺点是对创建的线程不能加以控制,也不能置其。由于程只有一个线程池,当然用程序域也只有一个线程池(对线),所以你将发现ThreadPool的成函数都static 当你首次ThreadPool.QueueUserWorkItemThreadPool.RegisterWaitForSingleObject等,便会线程池例。下面我就线程池当中的两函数作一介
[C#]
public static bool QueueUserWorkItem( //
用成功返回true
WaitCallback callBack,//
建的线用的委托
        object state  //
传递给委托的参数
)//
它的另一个重函数,只是委托不参数而已
      
此函数的作用是把要建的线程排线程池,当线程池的可用线程数不线程池有线程数的限制,缺身值为25),便建此线程,否就排线程池等到它有可用的线建。
[C#]
public static RegisteredWaitHandle RegisterWaitForSingleObject(
   WaitHandle waitObject,// 
要注册的 WaitHandle
   WaitOrTimerCallback callBack,// 
线用的委托
   object state,//
传递给委托的参数
   int TimeOut,//
毫秒,
   bool executeOnlyOnce file://
/否只行一次
); 
public delegate void WaitOrTimerCallback(
   object state,//
也即传递给委托的参数
   bool timedOut//true
表示由于超时调用,反之waitObject
);
      
此函数的作用是建一个等待线程,一旦用此函数便建此线程,在参数waitObject变为终止状或所定的时间TimeOut到了之前,它都阻塞得注意的一点是此阻塞ThreadWaitSleepJoin有很大的不同:当某ThreadWaitSleepJoin态时CPU会定期的醒它以轮询更新状信息,然后再次WaitSleepJoin线程的切可是很费资源的;而用此函数建的线不同,在触它运行之前,CPU不会切到此线程,它既不占用CPU时间又不浪费线程切换时间,但CPU又如何知道何运行它?实际线程池会生成一些线程用来监视这些触条件,一旦达到条件便启线程,当然线程本身也占用时间,但是如果你需多的等待线,使用线程池的优势就越加明下例:
static AutoResetEvent ev=new AutoResetEvent(false);
 public static int Main(string[] args)
 {  ThreadPool.RegisterWaitForSingleObject(
      ev,
      new WaitOrTimerCallback(WaitThreadFunc),
      4,
      2000,
      false//
表示次完成等待操作后都重置计时器,直到注等待
      );
  ThreadPool.QueueUserWorkItem (new WaitCallback (ThreadFunc),8);
  Thread.Sleep (10000);
   return 0;
 }
    public static void ThreadFunc(object b)
 { Console.WriteLine ("the object is {0}",b);
  for(int i=0;i<2;i++)
  { Thread.Sleep (1000);
   ev.Set();
  }
 }
 public static void WaitThreadFunc(object b,bool t)
 { Console.WriteLine ("the object is {0},t is {1}",b,t);
    }
其运行 

the object is 8

the object is 4,t is False

the object is 4,t is False

the object is 4,t is True

the object is 4,t is True

the object is 4,t is True

从以上果我可以看出线ThreadFunc运行了1次,而WaitThreadFunc运行了5次。我可以从WaitOrTimerCallback中的bool t参数判断启线程的原因:tfalse表示由于waitObject,否则则是由于超。另外我也可以通object b线传递一些参数。
3
 Timer
    
它适用于需周期性用的方法,它不在计时器的线程中运行,它在由系分配的线程中运行。Win32中的SetTimer方法似。它的构造
[C#]
public Timer(
   TimerCallback callback,//
所需用的方法
   object state,//
传递给callback的参数
   int dueTime,//
多久后callback
   int period//
用此方法的时间间
);// 
如果 dueTime 0 callback 立即行它的首次用。如果 dueTime  Infinite callback 用它的方法。计时器被禁用,但使用 Change 方法可以重新启用它。如果 period 0 Infinite,并且 dueTime  Infinite callback 用它的方法一次。计时的定期行被禁用,但使用 Change 方法可以重新启用它。如果 period  (0)  Infinite,并且 dueTime  Infinite callback 用它的方法一次。计时器的定期行被禁用,但使用 Change 方法可以重新启用它。
计时器之后若想改它的perioddueTime,我可以通过调TimerChange方法来改
[C#]
public bool Change(
   int dueTime,
   int period
);//
然所改两个参数对应Timer中的两参数
下例:public static int   Main(string[] args)

 {  Console.WriteLine ("period is 1000");
  Timer tm=new Timer (new TimerCallback (TimerCall),3,1000,1000);
  Thread.Sleep (2000);
  Console.WriteLine ("period is 500");
  tm.Change (0,800);
  Thread.Sleep (3000);
  return 0;
   }
   
 public static void TimerCall(object b)
 { 
  Console.WriteLine ("timercallback; b is {0}",b);
 }
其运行

period is 1000

timercallback;b is 3 

timercallback;b is 3 

period is 500

timercallback;b is 3 

timercallback;b is 3 

timercallback;b is 3 

timercallback;b is 3 
                        
总结
    
从以上的简单,我可以看出它各自使用的合:Thread适用于那些需对线复杂控制的合;ThreadPool于一些需要多个线程而又短任(如一些常于阻塞状线程);Timer适用于那些需周期性用的方法。只要我了解了它的使用特点,我就可以很好的选择合适的方

posted on 2008-04-24 16:00  谭洪星  阅读(414)  评论(0编辑  收藏  举报

导航