线程的基本操作

线程的基本操作

 

System.Threading命名空间下的Thread类提供了线程的基本操作.通过创建一个Thread对象,并执行他的Start()方法,可以新建并运行一个新的线程.新线程也需要一个入口,入口方法有ThreadStart委托和ParameterizedThreadStart定义,他们分别定义了无参数的入口方法和带参数的入口方法.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
namespace 线程的操作
{
    class Program
    {
        static void Main(string[] args)
        {
            ThreadStart ts = new ThreadStart(ThreadEntry);
            Thread worker = new Thread(ts);
            worker.Start();
            Console.WriteLine("Main Thread ends. ");
            Console.ReadKey();
 
        }
        static void ThreadEntry()
        {
            ShareResource resource = new ShareResource();
            resource.Add("Item3");
        }
    }
    public class ShareResource
    {
        public List<string> list = new List<string> { "Item0","Item1","Item2"};
        public void Add(string item)
        {
            Console.WriteLine("Add"+item);
            list.Add(item);
        }
    }
}


创建Thread对象之后,调用它的Start()方法,将会启动线程.Start()方法是非阻塞的,被调用之后会立即返回(Console.ReadKey()方法是一个阻塞方法的案例,在用户没有输入的情况下会一直等待,不会执行后续代码).调用Start(),新线程并非”立即”执行,这取决于操作系统的线程管理策略,只不过这个事件很短,感觉上像是新线程立即执行了.如果你多运行几次程序就会发现有不同的输出结果.

 

如果入口方法包含参数,则可以使用ParameterizedThreadStart委托:

        static void Main(string[] args)
        {
            ParameterizedThreadStart ts = new ParameterizedThreadStart(ThreadEntry);
            Thread worker = new Thread(ts);
            worker.Start();
            Console.WriteLine("Main Thread ends. ");
            Console.ReadKey();
 
        }
        static void ThreadEntry(object obj)
        {
            ShareResource resource = new ShareResource();
            string[] itemArray = (string[])obj;
            foreach (string item in itemArray)
            {
                resource.Add(item);
            }
            
        }


由于ParameterizedThreadStart定义的方法参数为object类型,因此在线程的入口方法ThreadEntry内部需要进行一下类型转换,转换为实际类型.

 

线程有以下几个常用的属性,后面的例子中大部分内容将会用到:

(1).ManagedThreadId属性,托管线程Id,在进程内唯一.ID与操作系统的线程Id是不同的,VS中调试运行程序,然后在菜单栏打开”调试”->”窗口”->”线程”,可以产看线程的操作系统Id和托管线程Id.

(2).Name属性,线程的名字,默认为空.由开发者设置,这个属性在调试的时候很有用,因为线程Id在每次运行的时候都会发生变化,但是Name属性是不变的.Name属性只能设置一次,当设置第二次时,会抛出异常.

(3).ThreadState属性,是一个位标记,描述了线程的当前运行状态.作为开发者,经常会用到的几个:Background,Unstarted,Running,WaitSleepJoin,Stopped.Requested结束的标记,存在于已经对线程发出请求(例如在代码中调用了Abort()方法),单线程尚未收到和执行的情况下,这几种状态存在的时间非常短暂,不易观察,对于开发者来说不重要.线程状态对于调试来说是有用的,但不实用它进行线程同步,因为在获取ThreadState属性的前后,线程的状态可能会发生改变.

 

 

查看当前线程

 

Thread类的CurrentThread静态属性返回了执行当前代码的线程对象:

        static void Main(string[] args)
        {
            Thread.CurrentThread.Name = "Main";
            ThreadStart ts = new ThreadStart(ThreadEntry);
            Thread worker = new Thread(ts);
            worker.Start();
            worker.Name = "Worker";
            Console.WriteLine(Thread.CurrentThread.Name);
        }
        static void ThreadEntry()
        {
            Console.WriteLine(Thread.CurrentThread.Name);
            
        }


对于Main()方法来说,执行该方法的线程是主线程,因此Main()方法中的Thread.CurrentThread.Name输出为Main;对于ThreadEntry()方法来说,执行他的线程是新建的worker线程,因此在ThreadEntry()方法中,Thread.CurrentThread.Name输出为Worker.

 

Sleep()方法

 

Thread类的Sleep()静态方法的作用是将当前线程暂停指定的一段时间.有两个重载方法,一个接受int类型的参数,表示暂停的毫秒数;另一个是接受一个TimeSpan.

 

Sleep()方法最常见的u欧勇是作为一个计时器,写在While循环中,比如进行两个系统间的数据同步.

 

提示:Windows窗体应用程序项目中有一个Timer控件,是实现上面应用常见更好的选择.

 

Sleep(),Join都是阻塞方法.当它们处于阻塞时,他不会占用CPU时间,知道阻塞结束,并开始执行后续代码的时候.

 

 

Interrupt()

 

        static void Main(string[] args)
        {
            Thread.CurrentThread.Name = "Main ";
            ThreadStart ts = new ThreadStart(ThreadEntry);
            Thread worker = new Thread(ts);
            worker.IsBackground = true;
            worker.Name = "Worker";
            worker.Start();
            worker.Interrupt();
            worker.Join();
            Console.WriteLine("{0}: End", Thread.CurrentThread.Name);
        }
        static void ThreadEntry()
        {
            string name= Thread.CurrentThread.Name;
            try
            {
                Thread.Sleep(10000);
            }
            catch (Exception ex)
            {
                Console.WriteLine("{0}: Catch {1}",name,ex.GetType());                
            }
            Console.WriteLine("{0}: End",name);
        }
    }

Thread类的Interrupt()实例方法用于唤醒处于睡眠或者等待中的线程,即状态为WaitSleepJoin的线程.线程被唤醒时,会抛出ThreadInterruptedException异常.

 

如果调用Intertupt()方法时,线程状态不是WaitSleepJoin,那么当后面某一时刻线程状态变为WaitSleepJoin,会立即抛出异常:

        static void Main(string[] args)
        {
            string name = "Main ";
            Thread.CurrentThread.Name = name;
            Thread.CurrentThread.Interrupt();
            try
            {
                Thread.Sleep(10000);
            }
            catch (Exception ex)
            {
                Console.WriteLine("{0}: Catch {1}",name,ex.GetType());                
            }
            Console.WriteLine("{0}: End",name);
        }
 


前台线程和后台线程

 

在默认情况下,新建的线程为前台线程,可以通过Thread类的实例属性IsBackground来查看.IsBackground设为true,则将线程设置为了后台线程(background Thread).前台线程和后台线程的区别是:所有的前台线程执行完毕之后,应用程序进程结束,而不论后台线程是否结束.

 

下面的代码将worker线程设置为了后台线程,并使用Sleep()方法延长了ThreadEntry()方法的执行时间:

        static void Main(string[] args)
        {
            string name = "Main ";
            Thread.CurrentThread.Name = name;
            ThreadStart ts = new ThreadStart(ThreadEntry);
            Thread worker = new Thread(ts);
            worker.Name = "Worker";
            worker.IsBackground = true;
            Console.WriteLine("{0}: Worker Status - {1}",name,worker.ThreadState);
            worker.Start();
 
            Console.WriteLine("{0}: Worker Status - {1}",name,worker.ThreadState);
            Console.WriteLine("{0}: End",name);
        }
        static void ThreadEntry()
        {
            string name = Thread.CurrentThread.Name;
            Console.WriteLine("{0}: Start",name);
            Thread.Sleep(100);//100毫秒
            Console.WriteLine("{0}: End",name);
        }
 


由于调用Start()方法后,线程实际启动时间的不确定,上面代码的输出可能是多种情况.

 

注意:后台线程的Background标记就相当于前台线程的Running标记,而不是Background,Running标记.

 

在创建一个线程之后,就应该对它的生存周期有完全的掌控,因此,不去管理后台线程,让他随着主线程的结束而终结是欠妥的,尤其是当后台线程还持有一些资源需要关闭的时候.当后台线程以这种方式退出时,即使位于finally块中的语句也不会执行.

 

 

Join()方法

有时候,一个线程的操作需要等待另一个线程执行完毕之后才能继续执行,比如主线程需要输出shareValue的值,而该值由worker线程计算得出:

    class Program
    {
        static string shareValue;
        static void Main(string[] args)
        {
            string name = "Main ";
            Thread.CurrentThread.Name = name;
            ThreadStart ts = new ThreadStart(ThreadEntry);
            Thread worker = new Thread(ts);
            worker.IsBackground = true;
            worker.Name = "Worker";
            worker.Start();
            Console.WriteLine("{0}: shareValue={1}",name,shareValue);
            Console.WriteLine("{0}: End",name);
        }
        static void ThreadEntry()
        {
            string name = Thread.CurrentThread.Name;
            Console.WriteLine("{0}: Start",name);
            Thread.Sleep(100);//100毫秒
            shareValue = "setted";
            Console.WriteLine("{0}: End",name);
        }
    }


可能会出现这样的情况,主线程已经执行结束了,worker线程还未计算完毕.此时,可以通过调用Sleep()方法来让主线程等待一段时间,worker线程执行完.

            worker.Start();
            Thread.Sleep(200);//让主线程等待200毫秒


如果依赖Sleep()来协调线程的执行顺序是一种很糟糕的做法.因为在正常的环境中,咱们无法得知ThreadEntry()的确切执行时间,比如shareValue来自一个远程的Web服务接口.此时,可以在worker线程上调用Join()方法,该方法会等待线程执行结束后,再继续执行后面的代码:

            worker.Start();
            worker.Join();//等待worker线程执行完毕


Join()可以用于线程同步.

 

 

Suspend()Resume()方法

 

Suspend()方法用于挂起线程,Resume()用于继续执行已挂起的线程.这两个方法可以用于线程同步,但它们和Start()有些类似,在调用Suspend()以后,线程也不会立即停止,而是执行到一个安全点(Safe Point)才会挂起.为了避免这种不确定性带来的问题,这两个方法就不用了.

 

 

线程异常

 

主线程会发生异常,工作线程肯定也会发生异常:

        static void Main(string[] args)
        {
            string name = "Main ";
            Thread.CurrentThread.Name = name;
            ThreadStart ts = new ThreadStart(ThreadEntry);
            Thread worker = new Thread(ts);
            worker.IsBackground = true;
            worker.Name = "Worker";
            worker.Start();
            Console.WriteLine("{0}: End", name);
            worker.Join();//等待worker线程执行完毕
 
        }
        static void ThreadEntry()
        {
            Thread.Sleep(100);
            throw new Exception();
        }


当工作线程(worker线程)抛出异常时,整个进程都会关闭,而不是仅结束抛出异常的线程(调用Abort()方法跑出的ThreadAbortException除外).如果注释掉Join(),就可以运行了.

因为worker是后台线程,还没来得及抛出异常,主线程(前台线程)已经执行完毕,应用程序退出了.不能把Start()放在try/catch,因为这段代码永远不会执行到catch,因为worker.Start()语句仍位于主线程,它并不会抛出异常.

 

 

Abort()方法

 

如果想强制推出一个线程,可以在它上面调用Abort()方法.调用Abort()会抛出一个异常,该异常很特殊,因为即使不捕获它也不会影响到整个进程:

        static void Main(string[] args)
        {
            string name = "Main ";
            Thread.CurrentThread.Name = name;
            ThreadStart ts = new ThreadStart(ThreadEntry);
            Thread worker = new Thread(ts);
            worker.IsBackground = true;
            worker.Name = "Worker";
            worker.Start();
            Console.WriteLine("{0}: End", name);
            worker.Abort();
            Console.WriteLine("{0}: Worker Status - {1}",name,worker.ThreadState);
            worker.Join();//等待worker线程执行完毕
 
        }
        static void ThreadEntry()
        {
            string name = Thread.CurrentThread.Name;
            Console.WriteLine("{0}: Start",name);
            Thread.Sleep(100);
            //Thread.CurrentThread.Abort();
            Console.WriteLine("{0}: End",name);
        }


输出结果为:

Main : End
Main : Worker Status - Aborted


worker线程没有产生任何的输出,因为它被主线程结束了.Abort()可以由当前执行代码在自身线程上调用,ThreadEntry(),注释掉了Thread.CurrentThread.Abort()语句,如果取消注释,并注释掉Main()中的worker.Abort(),得到的结果为:

Main : End
Worker: Start
Main : Worker Status - Background


worker线程在执行到Thread.CurrentThread.Abort()语句时就结束了,因此没有产生”Worker: End”输出.如果想捕获ThreadAbortException异常,修改一下ThreadEntry()方法假如try/catch语句:

        static void ThreadEntry()
        {
            string name = Thread.CurrentThread.Name;
            try
            {
                Console.WriteLine("{0}: Start", name);
                Thread.Sleep(100);
                //Thread.CurrentThread.Abort();
                Console.WriteLine("{0}: End", name);
            }
            catch (Exception ex)
            {
                Console.WriteLine("{0}: Catch {1}",name,ex.Message);             
            }
        }


Thread类还提供了一个AbortReset()静态方法,该方法可以让当前线程拒绝结束,修改一下上面的ThreadEntry()方法:

        static void ThreadEntry()
        {
            string name = Thread.CurrentThread.Name;
            try
            {
                Console.WriteLine("{0}: Start", name);
                Thread.Sleep(100);
            }
            catch (Exception ex)
            {
                Thread.ResetAbort();
                Console.WriteLine("{0}: Catch {1}",name,ex.Message);             
            }
            Console.WriteLine("{0}: Status - {1}",name,Thread.CurrentThread.ThreadState);
            Console.WriteLine("{0}: End",name);
        }


Worker线程在执行到Thread.ResetAbort()再次复活了,try/catch后面的语句会继续执行.

你可能感兴趣的:(.net)