C# 多线程操作整理 (System.Threading)

Thread 类

创建并控制线程,设置其优先级并获取其状态。

命名空间:System.Threading 程序集:mscorlib(在 mscorlib.dll 中)

概述与概念

C#支持通过多线程并行地执行代码,一个线程有它独立的执行路径,能够与其它的线程同时地运行。一个C#程序开始于一个单线程,这个单线程是被CLR和操作系统(也称为“主线程”)自动创建的,并具有多线程创建额外的线程。

1.使用线程的情况

  • ①.程序需要执行和两个和多个任务
  • ②.程序要等待某事件的发生:例如用户输入、文件操作、网络操作和搜索
  • ③.后台程序

2.多线程的并发执行 如果有多个线程在执行,单CPU只有一个,到底执行的哪个?

  • ①.如果一个线程连续占用CPU资源时间过长,其它的资源得不到执行,      则系统会强制的切换执行其它线程。(强制剥夺)
  • ②.如果一个线程没事可做、CPU可执行其它线程。(主动放弃)
  • ③.这是由操作系统的调度机制决定的,不同的操作系统调度机制不一样。    一般无法精确的预料多线程的执行顺序,在程序设计的时候应特别注意

3.创建并启动线程

ThreadStart 线程启动委托名=new ThreadStart(方法名);

Thread 线程实例名=new Thread(线程启动委托名); 线程实例名.Start();

4.终止线程

  • ①.线程实例名.Abort();用此方法的后果是不可恢复的终止线程。
  • ②.线程实例名.Interrupt();中断后可恢复

5.休眠线程

  • ①.线程实例名.Sleep();     当线程Sleep时,系统就立即退出执行队列一段时间,当睡眠结束时,系统会产生一个时钟中断,从而     使线程回到执行队列中,从而恢复线程的执行。

6.挂起/恢复线程

  • ①.线程实例名.Suspend();挂起     与线程休眠不同,线程的挂起不会使线程立即停止执行,直到线程到达安全点之后它才可以将该线程挂起,如果线程尚未启动或已经停止,则它将不能挂起。
  • ②.线程实例名.Resume();恢复      将使一个线程跳出挂起状态并使该线程继续执行。     一个线程不能对另一个线程调用Sleep() ,但是一个线程可以对另一个线程调用Suspend()。     还可以使用许多其它的方式来阻塞线程。例如,可以通过调用 Thread.Join 使一个线程等待另一个线程 (子线程)停止。使用Monitor.Wait使一个线程等待访问一个同步对象。

7.串行化线程

  • ①.线程实例名.jion();     例如在主线程中插入t.jion();      主线程执行到这条语句后,主线程(当前线程)立即进入阻塞状态.直到t运行完后阻塞状态才解除。相当于把t的任务插入或串联到主线程中,把两条线索串联成一条线索

8.线程的锁定机制 线程的锁定机制可以保证每次只有一个线程可以访问共享资源。 使用关键字lock

  • ①.lock语句的语法      lock(对象引用)语句块;
  • ②.lock语句的功能      当对象被lock 锁定时,访问该线程的其它线程会进入等待的状态。
  • ③.对象锁机制保证了对象访问的完整性:只有一个线程完成操作后,其它的线程才能进行操作。
  • ④.一般情况下,当一个线程写某个变量,而同时可能有其它的线程读或写这个变量时,为了保持数据的一 致性应该使用锁定机制。
  • ⑤.线程的安全性      线程安全性就是保护的类的成员和代码的安全,从而使他们不会同时被几个线程中断,使用锁定机制。
  • ⑥.多线程公用一个对象时,就不应该使用lock关键字了,这里Monitor,Monitor提供了使线程共享资源的方 案。 Monitor类可以锁定一个对象,一个线程只有得到这把锁才可以对该对象进行操作。 如: Monitor.Enter(obj);
    Monitor.Exit(obj);
  • ⑦.临界区和锁 当谈论多线程应用程序的时候,首先应该想到的就是并发性问题。尽管这对于同时执行多个任务的程序是很有用的,但通常都是危险的。为了解决这个问题,在C#中提出了临界区和锁的概念。在程序设计中,临界区是一块在任何时候只能有一个进程进入的区域。在C#中通过语句lock来声明临界区。lock声明后面的代码,不管是以行还是一块代码,在同一时间最多只能有一个进程执行。

9.线程的优先级具有不可靠性,就是说不能用优先级来控制线程的执行顺序。

10.后台线程

  • ①.什么是后台线程?比起应用程序的主图形用户界面(GUI)线程来说,这些线程以较低的优先权在不同的过程中运行着。对于不能立即执行结束, 又不想一直等待的任务,后台线程能很好的胜任。在C#中,把线程对象的  IsBackground属性设为true,该线程即为后台线程。    后台线程跟前台线程只有一个区别,那就是后台线程不妨碍程序的终止。一旦一个进程所有的前台线程都终止后,CLR将通过调用任意一个存活中的后台进程的Abort()方法来彻底终止进程。注意:后台线程不能直接操作所在进程之外的数据引用。
  • ②.怎样与后台线程通讯?运用MethodInvoker委派实体。也可在初始化(构造函数)中加入下面一句即可实现通讯:

Control.CheckForIllegalCrossThreadCalls = False;

要使用MethodInvoker委派,需要三个条件:

  •   a.一个创建委派的后台线程
Thread thread=new Thread(new ThreadStart(Run));

thread.IsBackground=true;  //把Thread设为后台线程

thread.Start();
  •   b.一个用作后台线程与前台可视化单元的接口的类级方法
 public void Run()
        {
            int count = 0;
            try
            {
                MethodInvoker mi = new MethodInvoker(this.UpdateLabel);
                //创建一个委托,UpdateLabel是该委托所托管的代码,必须是声明为void 且不接受任何参数的任何方法。
                while (true)
                {
                    count++;
                    //this.Invoke(mi);//同步执行委托
                    this.BeginInvoke(mi);//异步执行委托
                    Thread.Sleep(500);
                }
            }
            catch (ThreadInterruptedException e)
            {
                Console.WriteLine("Interruption Exception in Thread:{0}", e);
            }
            catch (Exception ex)
            {
                Console.WriteLine("Exception in Thread:{0}", ex);
            }
        }
  •   c.一个应用程序中可以更新的可视化单元
public void UpdateLabel()
{     
    label1.Text=count.ToString();   
}

.NET Windows Forms C# 线程中的全局异常处理

异常处理

任何线程创建范围内try/catch/finally块,当线程开始执行便不再与其有任何关系。考虑下面的程序:

public static void Main() {
try {
       new Thread (Go).Start();
}
catch (Exception ex) {
       // 不会在这得到异常
       Console.WriteLine ("Exception!");
}

static void Go() { throw null; }
}

这里try/catch语句一点用也没有,新创建的线程将引发NullReferenceException异常。当你考虑到每个线程有独立的执行路径的时候,便知道这行为是有道理的,补救方法是在线程处理的方法内加入他们自己的异常处理:

public static void Main() {
      new Thread (Go).Start();
}

static void Go() {
try {
       ...
       throw null;         // 这个异常在下面会被捕捉到
       ...
}
catch (Exception ex) {
       记录异常日志,并且或通知另一个线程
       我们发生错误
       ...
}

从.NET 2.0开始,任何线程内的未处理的异常都将导致整个程序关闭,这意味着忽略异常不再是一个选项了。因此为了避免由未处理异常引起的程序崩溃,try/catch块需要出现在每个线程进入的方法内,至少要在产品程序中应该如此。对于经常使用“全局”异常处理的Windows Forms程序员来说,这可能有点麻烦,像下面这样:

using System;
using System.Threading;
using System.Windows.Forms;

static class Program {
static void Main() {
       Application.ThreadException += HandleError;
       Application.Run (new MainForm());
}

static void HandleError (object sender, ThreadExceptionEventArgs e) {
       记录异常或者退出程序或者继续运行...
}
}

Application.ThreadException事件在异常被抛出时触发,以一个Windows信息(比如:键盘,鼠标活着 “paint” 等信息)的方式,简言之,一个Windows Forms程序的几乎所有代码。虽然这看起来很完美,它使人产生一种虚假的安全感——所有的异常都被中央异常处理捕捉到了。由工作线程抛出的异常便是一个没有被Application.ThreadException捕捉到的很好的例外。(在Main方法中的代码,包括构造器的形式,在Windows信息开始前先执行)

.NET framework为全局异常处理提供了一个更低级别的事件:AppDomain.UnhandledException,这个事件在任何类型的程序(有或没有用户界面)的任何线程有任何未处理的异常触发。尽管它提供了好的不得已的异常处理解决机制,但是这不意味着这能保证程序不崩溃,也不意味着能取消.NET异常对话框。