zoukankan      html  css  js  c++  java
  • C# 线程的定义和使用

    C# 线程的定义和使用

    一、C# Thread类的基本用法

        通过System.Threading.Thread类可以开始新的线程,并在线程堆栈中运行静态或实例方法。可以通过Thread类的的构造方法传递一个无参数,并且不返回值(返回void)的委托(ThreadStart),这个委托的定义如下:

        [ComVisibleAttribute(true)]

        public delegate void ThreadStart()

        我们可以通过如下的方法来建立并运行一个线程。

    复制代码
     1 using System;  
     2 using System.Collections.Generic;  
     3 using System.Linq;  
     4 using System.Text;  
     5 using System.Threading;  
     6  
     7 namespace MyThread  
     8 {  
     9     class Program  
    10     {  
    11         public static void myStaticThreadMethod()  
    12         {  
    13             Console.WriteLine("myStaticThreadMethod");  
    14         }  
    15         static void Main(string[] args)  
    16         {  
    17             Thread thread1 = new Thread(myStaticThreadMethod);  
    18             thread1.Start();  // 只要使用Start方法,线程才会运行  
    19         }  
    20     }  
    21 }  
    22  
    复制代码

        除了运行静态的方法,还可以在线程中运行实例方法,代码如下:

    复制代码
     1 using System;  
     2 using System.Collections.Generic;  
     3 using System.Linq;  
     4 using System.Text;  
     5 using System.Threading;  
     6  
     7 namespace MyThread  
     8 {  
     9     class Program  
    10     {  
    11         public void myThreadMethod()  
    12         {  
    13             Console.WriteLine("myThreadMethod");  
    14         }  
    15         static void Main(string[] args)  
    16         {  
    17             Thread thread2 = new Thread(new Program().myThreadMethod);  
    18             thread2.Start();  
    19         }  
    20     }  
    21 }  
    22  
    复制代码

        如果读者的方法很简单,或出去某种目的,也可以通过匿名委托或Lambda表达式来为Thread的构造方法赋值,代码如下:

    1 Thread thread3 = new Thread(delegate() { Console.WriteLine("匿名委托"); });  
    2 thread3.Start();  
    3  
    4 Thread thread4 = new Thread(( ) => { Console.WriteLine("Lambda表达式"); });  
    5 thread4.Start();  
    6  

        其中Lambda表达式前面的( )表示没有参数。

        为了区分不同的线程,还可以为Thread类的Name属性赋值,代码如下:

    1 Thread thread5 = new Thread(()=>{ Console.WriteLine(Thread.CurrentThread.Name); });  
    2 thread5.Name = "我的Lamdba";  
    3 thread5.Start(); 

        如果将上面thread1至thread5放到一起执行,由于系统对线程的调度不同,输出的结果是不定的,如图1是一种可能的输出结果。

    一种可能的输出结果 

    图1

    二、 定义一个线程类

        我们可以将Thread类封装在一个MyThread类中,以使任何从MyThread继承的类都具有多线程能力。MyThread类的代码如下:

    复制代码
     1 using System;  
     2 using System.Collections.Generic;  
     3 using System.Linq;  
     4 using System.Text;  
     5 using System.Threading;  
     6 namespace MyThread  
     7 {  
     8    abstract class MyThread  
     9     {  
    10        Thread thread = null;  
    11  
    12        abstract public void run();      
    13  
    14         public void start()  
    15         {  
    16             if (thread == null)  
    17                 thread = new Thread(run);  
    18             thread.Start();  
    19         }  
    20     }  
    21 }  
    22  
    复制代码

        可以用下面的代码来使用MyThread类。

    复制代码
     1 class NewThread : MyThread  
     2 {  
     3       override public void run()  
     4       {  
     5           Console.WriteLine("使用MyThread建立并运行线程");  
     6       }  
     7   }  
     8  
     9   static void Main(string[] args)  
    10   {  
    11  
    12       NewThread nt = new NewThread();  
    13       nt.start();  
    14   }  
    15  
    复制代码

        我们还可以利用MyThread来为线程传递任意复杂的参数。详细内容见下节。

    三、C# Thread类:为线程传递参数

        Thread类有一个带参数的委托类型的重载形式。这个委托的定义如下:

        [ComVisibleAttribute(false)]

        public delegate void ParameterizedThreadStart(Object obj)

        这个Thread类的构造方法的定义如下:

    1. public Thread(ParameterizedThreadStart start); 

        下面的代码使用了这个带参数的委托向线程传递一个字符串参数:

    复制代码
     1 public static void myStaticParamThreadMethod(Object obj)  
     2 {  
     3     Console.WriteLine(obj);  
     4 }  
     5  
     6 static void Main(string[] args)  
     7 {  
     8       Thread thread = new Thread(myStaticParamThreadMethod);  
     9       thread.Start("通过委托的参数传值");  
    10 }  
    11  
    复制代码

        要注意的是,如果使用的是不带参数的委托,不能使用带参数的Start方法运行线程,否则系统会抛出异常。但使用带参数的委托,可以使用thread.Start()来运行线程,这时所传递的参数值为null。

        也可以定义一个类来传递参数值,如下面的代码如下:

    复制代码
     1 class MyData  
     2 {  
     3     private String d1;  
     4     private int d2;  
     5     public MyData(String d1, int d2)  
     6     {  
     7           this.d1 = d1;  
     8           this.d2 = d2;  
     9     }  
    10     public void threadMethod()  
    11     {  
    12           Console.WriteLine(d1);  
    13           Console.WriteLine(d2);  
    14     }  
    15 }  
    16  
    17 MyData myData = new MyData("abcd",1234);  
    18 Thread thread = new Thread(myData.threadMethod);  
    19 thread.Start();  
    20  
    复制代码

        如果使用在第二节定义的MyThread类,传递参数会显示更简单,代码如下:

    复制代码
    class NewThread : MyThread  
    {  
        private String p1;  
        private int p2;  
        public NewThread(String p1, int p2)  
        {  
            this.p1 = p1;  
            this.p2 = p2;  
        }  
     
        override public void run()  
        {  
            Console.WriteLine(p1);  
            Console.WriteLine(p2);  
        }  
    }  
     
    NewThread newThread = new NewThread("hello world", 4321);  
    newThread.start();  
     
    复制代码

    四、前台和后台线程

        使用Thread建立的线程默认情况下是前台线程,在进程中,只要有一个前台线程未退出,进程就不会终止。主线程就是一个前台线程。而后台线程不管线程是否结束,只要所有的前台线程都退出(包括正常退出和异常退出)后,进程就会自动终止。一般后台线程用于处理时间较短的任务,如在一个Web服务器中可以利用后台线程来处理客户端发过来的请求信息。而前台线程一般用于处理需要长时间等待的任务,如在Web服务器中的监听客户端请求的程序,或是定时对某些系统资源进行扫描的程序。下面的代码演示了前台和后台线程的区别。

    复制代码
    1 public static void myStaticThreadMethod()  
    2 {  
    3     Thread.Sleep(3000);  
    4 }  
    5  
    6 Thread thread = new Thread(myStaticThreadMethod);  
    7 // thread.IsBackground = true;  
    8 thread.Start();  
    复制代码

        如果运行上面的代码,程序会等待3秒后退出,如果将注释去掉,将thread设成后台线程,则程序会立即退出。

        要注意的是,必须在调用Start方法之前设置线程的类型,否则一但线程运行,将无法改变其类型。

        通过BeginXXX方法运行的线程都是后台线程。

    五、C# Thread类:判断多个线程是否都结束的两种方法

        确定所有线程是否都完成了工作的方法有很多,如可以采用类似于对象计数器的方法,所谓对象计数器,就是一个对象被引用一次,这个计数器就加1,销毁引用就减1,如果引用数为0,则垃圾搜集器就会对这些引用数为0的对象进行回收。

        方法一:线程计数器

        线程也可以采用计数器的方法,即为所有需要监视的线程设一个线程计数器,每开始一个线程,在线程的执行方法中为这个计数器加1,如果某个线程结束(在线程执行方法的最后为这个计数器减1),为这个计数器减1。然后再开始一个线程,按着一定的时间间隔来监视这个计数器,如是棕个计数器为0,说明所有的线程都结束了。当然,也可以不用这个监视线程,而在每一个工作线程的最后(在为计数器减1的代码的后面)来监视这个计数器,也就是说,每一个工作线程在退出之前,还要负责检测这个计数器。使用这种方法不要忘了同步这个计数器变量啊,否则会产生意想不到的后果。

        方法二:使用Thread.join方法

        join方法只有在线程结束时才继续执行下面的语句。可以对每一个线程调用它的join方法,但要注意,这个调用要在另一个线程里,而不要在主线程,否则程序会被阻塞的。

        个人感觉这种方法比较好。

        线程计数器方法演示:

    复制代码
     1     class ThreadCounter : MyThread  
     2     {  
     3         private static int count = 0;  
     4         private int ms;  
     5         private static void increment()  
     6         {  
     7             lock (typeof(ThreadCounter))  // 必须同步计数器  
     8             {  
     9                 count++;  
    10             }  
    11         }  
    12         private static void decrease()  
    13         {  
    14             lock (typeof(ThreadCounter))  
    15             {  
    16                 count--;  
    17             }  
    18         }  
    19         private static int getCount()  
    20         {  
    21             lock (typeof(ThreadCounter))  
    22             {  
    23                 return count;  
    24             }  
    25         }  
    26         public ThreadCounter(int ms)  
    27         {  
    28             this.ms = ms;  
    29         }  
    30         override public void run()  
    31         {  
    32             increment();  
    33             Thread.Sleep(ms);  
    34             Console.WriteLine(ms.ToString()+"毫秒任务结束");  
    35             decrease();  
    36             if (getCount() == 0)  
    37                 Console.WriteLine("所有任务结束");  
    38         }  
    39     }  
    40  
    41  
    42 ThreadCounter counter1 = new ThreadCounter(3000);  
    43 ThreadCounter counter2 = new ThreadCounter(5000);  
    44 ThreadCounter counter3 = new ThreadCounter(7000);  
    45  
    46 counter1.start();  
    47 counter2.start();  
    48 counter3.start();  
    49  
    复制代码

        上面的代码虽然在大多数的时候可以正常工作,但却存在一个隐患,就是如果某个线程,假设是counter1,在运行后,由于某些原因,其他的线程并未运行,在这种情况下,在counter1运行完后,仍然可以显示出“所有任务结束”的提示信息,但是counter2和counter3还并未运行。为了消除这个隐患,可以将increment方法从run中移除,将其放到ThreadCounter的构造方法中,在这时,increment方法中的lock也可以去掉了。代码如:

    1 public ThreadCounter(int ms)  
    2 {  
    3     this.ms = ms;  
    4     increment();  
    5 } 

        运行上面的程序后,将显示如图2的结果。

    图2 

    图2

        使用Thread.join方法演示

    复制代码
     1 private static void threadMethod(Object obj)  
     2 {  
     3     Thread.Sleep(Int32.Parse(obj.ToString()));  
     4     Console.WriteLine(obj + "毫秒任务结束");  
     5 }  
     6 private static void joinAllThread(object obj)  
     7 {  
     8     Thread[] threads = obj as Thread[];  
     9     foreach (Thread t in threads)  
    10         t.Join();  
    11     Console.WriteLine("所有的线程结束");  
    12 }  
    13  
    14 static void Main(string[] args)  
    15 {  
    16     Thread thread1 = new Thread(threadMethod);  
    17     Thread thread2 = new Thread(threadMethod);  
    18     Thread thread3 = new Thread(threadMethod);  
    19  
    20      thread1.Start(3000);  
    21      thread2.Start(5000);  
    22      thread3.Start(7000);  
    23  
    24      Thread joinThread = new Thread(joinAllThread);  
    25      joinThread.Start(new Thread[] { thread1, thread2, thread3 });  
    26  
    27 }  
    28  
    复制代码

        在运行上面的代码后,将会得到和图2同样的运行结果。上述两种方法都没有线程数的限制,当然,仍然会受到操作系统和硬件资源的限制。

     
     
    分类: C#
    标签: 线程
  • 相关阅读:
    668. Kth Smallest Number in Multiplication Table
    658. Find K Closest Elements
    483. Smallest Good Base
    475. Heaters
    454. 4Sum II
    441. Arranging Coins
    436. Find Right Interval
    410. Split Array Largest Sum
    392. Is Subsequence
    378. Kth Smallest Element in a Sorted Matrix
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/3436506.html
Copyright © 2011-2022 走看看