进程和线程(线程是轻量级进程)(中)

创建线程

线程是通过扩展Thread类创建的。扩展的 Thread 类调用Start()方法来开始子线程的执行。

using System;
using System.Threading;

namespace MulityThreadNote
{
    class Program
    {
        static void Main(string[] args)
        {
            Thread t1 = new Thread(new ThreadStart(PrintNumbers));//无参数的委托
            t1.Start();
            
            Thread t2 = new Thread(new ParameterizedThreadStart(PrintNumbers));//有参数的委托
            t2.Start(10);
            Console.ReadLine();
        }

        static void PrintNumbers()
        {
            Console.WriteLine("无参数委托Starting...");
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine(i);
            }
        }

        //注意:要使用ParameterizedThreadStart,定义的参数必须为object
        //如果使用的是不带参数的委托,不能使用带参数的Start方法运行线程,否则系统会抛出异常。但使用带参数的委托,可以使用thread.Start()来运行线程,这时所传递的参数值为null。
        static void PrintNumbers(object count)
        {
            Console.WriteLine("有参数委托Starting...");
            for (int i = 0; i < Convert.ToInt32(count); i++)
            {
                Console.WriteLine(i);
            }
        }
    }
}

注释:我们只需指定在不同线程运行的方法名,而C#编译器会在后台创建这些对象

 

 

如果为了简单,也可以通过匿名委托或Lambda表达式来为Thread的构造方法赋值

static void Main(string[] args)
 {
       //通过匿名委托创建
       Thread thread1 = new Thread(delegate() { Console.WriteLine("我是通过匿名委托创建的线程"); });
       thread1.Start();
       //通过Lambda表达式创建
       Thread thread2 = new Thread(() => Console.WriteLine("我是通过Lambda表达式创建的委托"));
       thread2.Start();
       Console.ReadKey();
 }

运行结果:

管理线程

Thread类提供了各种管理线程的方法。

下面的实例演示了Sleep()方法的使用,用于在一个特定的时间暂停线程。

using System;
using System.Threading;

namespace MultithreadingApplication
{
    class ThreadCreationProgram
    {
        public static void CallToChildThread()
        {
            Console.WriteLine("Child thread starts");
            // 线程暂停 5000 毫秒
            int sleepfor = 5000; 
            Console.WriteLine("Child Thread Paused for {0} seconds", sleepfor / 1000);
            Thread.Sleep(sleepfor);
            Console.WriteLine("Child thread resumes");
        }
        
        static void Main(string[] args)
        {
            ThreadStart childref = new ThreadStart(CallToChildThread);
            Console.WriteLine("In Main: Creating the Child thread");
            Thread childThread = new Thread(childref);
            childThread.Start();
            Console.ReadKey();
        }
    }
}

注释:也可使用Thread.Sleep(TimeSpan.FromSeconds(5)); 暂停线程

运行结果:等待5秒后出现 Child thread resumes

销毁线程

Abort()方法用于销毁线程。

通过抛出ThreadAbortException在运行时中止线程。这个异常不能被捕获,如果有finally块,控制会被送至finally块。

下面的程序说明了这点:

using System;
using System.Threading;

namespace MultithreadingApplication
{
    class ThreadCreationProgram
    {
        public static void CallToChildThread()
        {
            try
            {
                Console.WriteLine("Child thread starts");
                // 计数到 10
                for (int counter = 0; counter <= 10; counter++)
                {
                    Thread.Sleep(500);
                    Console.WriteLine(counter);
                }
                Console.WriteLine("Child Thread Completed");
            }
            catch (ThreadAbortException e)
            {
                Console.WriteLine("Thread Abort Exception");
            }
            finally
            {
                Console.WriteLine("Couldn't catch the Thread Exception");
            }
        }
        
        static void Main(string[] args)
        {
            ThreadStart childref = new ThreadStart(CallToChildThread);
            Console.WriteLine("In Main: Creating the Child thread");
            Thread childThread = new Thread(childref);
            childThread.Start();
            // 停止主线程一段时间
            Thread.Sleep(2000);
            // 现在中止子线程
            Console.WriteLine("In Main: Aborting the Child thread");
            childThread.Abort();
            Console.ReadKey();
        }
    }
}
View Code

运行结果:

线程等待

using System;
using System.Threading;

namespace MulityThreadNote
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Main:Starting...");
            Thread t = new Thread(PrintNumbersWithDelay);
            t.Start();
            t.Join();   //使用Join等待t完成
            PrintNumbers();
            Console.WriteLine("THread Complete");
            Console.ReadLine();
        }

        static void PrintNumbers()
        {
            Console.WriteLine("PrintNumbers:Starting...");
            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine(i);
            }
        }

        static void PrintNumbersWithDelay()
        {
            Console.WriteLine("PrintNumbersWithDelay:Starting...");
            for (int i = 0; i < 5; i++)
            {
                Thread.Sleep(TimeSpan.FromSeconds(2));
                Console.WriteLine(i);
            }
        }
    }
}

注释:使用t.Join();   等待t完成

运行结果:

检测线程状态

using System;
using System.Threading;

namespace DelegateDemo
{
    class Program
    {
        private static void PrintNumbersWithStatus()
        {
            Console.WriteLine("PrintNumbersWithStatus:Starting...");
            Console.WriteLine("PrintNumbersWithStatus:" + Thread.CurrentThread.ThreadState.ToString());//获取当前线程状态
            for (int i = 0; i < 5; i++)
            {
                Thread.Sleep(TimeSpan.FromSeconds(2));
                Console.WriteLine("PrintNumbersWithStatus:" + i);
            }
        }
        private static void DoNothing()
        {
            Thread.Sleep(TimeSpan.FromSeconds(2));
        }
        static void Main(string[] args)
        {
            Console.WriteLine("Main:Start Program...");
            Thread t1 = new Thread(PrintNumbersWithStatus);
            Thread t2 = new Thread(DoNothing);
            Console.WriteLine("Main1:" + t1.ThreadState.ToString());//获取实例线程状态
            t2.Start();
            t1.Start();
            for (int i = 0; i < 30; i++)
            {
                Console.WriteLine("Main2:" + t1.ThreadState.ToString());
            }
            Thread.Sleep(TimeSpan.FromSeconds(5));
            t1.Abort();
            Console.WriteLine("thread t1 has been aborted");
            Console.WriteLine("Main3:" + t1.ThreadState.ToString());
            Console.WriteLine("Main4:" + t2.ThreadState.ToString());

            Console.ReadKey();
        }
    }
}
View Code

注释:使用Thread.ThreadState获取线程的运行状态。ThreadState是一个C#枚举。谨记:不要在程序中使用线程终止,否则可能会出现意想不到的结果

运行结果:

前台线程和后台线程

前台线程:只有所有的前台线程都结束,应用程序才能结束。默认情况下创建的线程都是前台线程

后台线程:只要所有的前台线程结束,后台线程自动结束。通过Thread.IsBackground设置后台线程。必须在调用Start方法之前设置线程的类型,否则一旦线程运行,将无法改变其类型。

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

using System;
using System.Diagnostics;
using System.Threading;

namespace MulityThreadNote
{
    class Program
    {
        static void Main(string[] args)
        {                   
            //演示前台、后台线程
            BackGroundTest background = new BackGroundTest(10);
            //创建前台线程
            Thread fThread = new Thread(new ThreadStart(background.RunLoop));
            //给线程命名
            fThread.Name = "前台线程";
            
            BackGroundTest background1 = new BackGroundTest(20);
            //创建后台线程
            Thread bThread = new Thread(new ThreadStart(background1.RunLoop));
            bThread.Name = "后台线程";
            //设置为后台线程
            bThread.IsBackground = true;

            //启动线程
            fThread.Start();
            bThread.Start();
        }
    }

    class BackGroundTest
    {
        private int Count;
        public BackGroundTest(int count)
        {
            this.Count = count;
        }
        public void RunLoop()
        {
            //获取当前线程的名称
            string threadName = Thread.CurrentThread.Name;
            for (int i = 0; i < Count; i++)
            {
                Console.WriteLine("{0}计数:{1}",threadName,i.ToString());
                //线程休眠500毫秒
                Thread.Sleep(1000);
            }
            Console.WriteLine("{0}完成计数",threadName);
            
        }
    }
}
View Code

运行结果:前台线程执行完,后台线程未执行完,程序自动结束。

bThread.IsBackground = true注释掉,运行结果:主线程执行完毕后(Main函数),程序并未结束,而是要等所有的前台线程结束以后才会结束。

后台线程一般用于处理不重要的事情,应用程序结束时,后台线程是否执行完成对整个应用程序没有影响。如果要执行的事情很重要,需要将线程设置为前台线程。

向线程传递参数

using System;
using System.Diagnostics;
using System.Threading;

namespace MulityThreadNote
{
    class Program
    {
        static void Main(string[] args)
        {
            ThreadSample sample = new ThreadSample(5);

            Thread t1 = new Thread(sample.CountNumbers);
            t1.Name = "ThreadOne";
            t1.Start();
            t1.Join();
            Console.WriteLine("--------------------------");

            Thread t2 = new Thread(Count);
            t2.Name = "ThreadTwo";
            t2.Start(3);
            t2.Join();
            Console.WriteLine("--------------------------");

            //使用lambda表达式引用另一个C#对方的方式被称为闭包。当在lambda表达式中使用任何局部变量时,C#会生成一个类,并将该变量作为该类的一个属性,但是我们无须定义该类,C#编译器会自动帮我们实现
            Thread t3 = new Thread(()=> CountNumbers(5));
            t3.Name = "ThreadThree";
            t3.Start();
            t3.Join();
            Console.WriteLine("--------------------------");

            int i = 10;
            Thread t4 = new Thread(() => PrintNumber(i));
            
            i = 20;
            Thread t5 = new Thread(() => PrintNumber(i));
            t4.Start();
            t5.Start();
            //t4, t5都会输出20, 因为t4,t5没有Start之前i已经变成20了
            Console.ReadKey();
        }

        static void Count(object iterations)
        {
            CountNumbers((int)iterations);
        }

        static void CountNumbers(int iterations)
        {
            for (int i = 1; i <= iterations; i++)
            {
                Thread.Sleep(TimeSpan.FromSeconds(0.5));
                Console.WriteLine($"{Thread.CurrentThread.Name} prints {i}");
            }
        }

        static void PrintNumber(int number)
        {
            Console.WriteLine(number);
        }
    }

    class ThreadSample
    {
        private readonly int _iteration;

        public ThreadSample(int iteration)
        {
            _iteration = iteration;
        }

        public void CountNumbers()
        {
            for (int i = 1; i <= _iteration; i++)
            {
                Thread.Sleep(TimeSpan.FromSeconds(0.5));
                Console.WriteLine($"{Thread.CurrentThread.Name} prints {i}");
            }
        }
    }
}
View Code

注释也可以使用ThreadStart传递参数

线程同步

所谓同步:是指在某一时刻只有一个线程可以访问变量。

如果不能确保对变量的访问是同步的,就会产生错误。

c#为同步访问变量提供了一个非常简单的方式,即使用c#语言的关键字Lock,它可以把一段代码定义为互斥段,互斥段在一个时刻内只允许一个线程进入执行,而其他线程必须等待。在c#中,关键字Lock定义如下:

lock(expression)
{
   statement_block
}

expression代表你希望跟踪的对象:

           如果你想保护一个类的实例,一般地,你可以使用this

           如果你想保护一个静态变量(如互斥代码段在一个静态方法内部),一般使用类名就可以了

而statement_block就算互斥段的代码,这段代码在一个时刻内只可能被一个线程执行。

以书店卖书为例

class Program
    {
        static void Main(string[] args)
        {                   
            BookShop book = new BookShop();
            //创建两个线程同时访问Sale方法
            Thread t1 = new Thread(new ThreadStart(book.Sale));
            Thread t2 = new Thread(new ThreadStart(book.Sale));
            //启动线程
            t1.Start();
            t2.Start();
            Console.ReadKey();
        }
    }

    class BookShop
    {
        //剩余图书数量
        public int num = 1;
        public void Sale()
        {
            int tmp = num;
            if (tmp > 0)//判断是否有书,如果有就可以卖
            {
                Thread.Sleep(1000);
                num -= 1;
                Console.WriteLine("售出一本图书,还剩余{0}本", num);
            }
            else
            {
                Console.WriteLine("没有了");
            }
        }
    }

运行结果:

从运行结果可以看出,两个线程同步访问共享资源,没有考虑同步的问题,结果不正确。

考虑线程同步,改进后的代码:

class Program
    {
        static void Main(string[] args)
        {                   
            BookShop book = new BookShop();
            //创建两个线程同时访问Sale方法
            Thread t1 = new Thread(new ThreadStart(book.Sale));
            Thread t2 = new Thread(new ThreadStart(book.Sale));
            //启动线程
            t1.Start();
            t2.Start();
            Console.ReadKey();
        }
    }

    class BookShop
    {
        //剩余图书数量
        public int num = 1;
        public void Sale()
        {
            //使用lock关键字解决线程同步问题
            lock (this)
            {
                int tmp = num;
                if (tmp > 0)//判断是否有书,如果有就可以卖
                {
                    Thread.Sleep(1000);
                    num -= 1;
                    Console.WriteLine("售出一本图书,还剩余{0}本", num);
                }
                else
                {
                    Console.WriteLine("没有了");
                }
            }
        }
    }

运行结果:

处理异常

using System;
using System.Threading;

namespace DelegateDemo
{
    class Program
    {
        static void BadFaultyThread()
        {
            Console.WriteLine("Starting a faulty thread.....");
            Thread.Sleep(TimeSpan.FromSeconds(5));
            //这个异常主线程无法捕捉到,因为是在子线程抛出的异常。需要在子线程中加入try...catch捕获异常
            throw new Exception("BadFaultyThread:Boom!");
        }
        static void FaultyThread()
        {
            try
            {
                Console.WriteLine("Starting a faulty thread...");
                Thread.Sleep(TimeSpan.FromSeconds(5));
                throw new Exception("FaultyThread:Boom");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Exception handled: {ex.Message}");
            }
        }
        static void Main(string[] args)
        {
            Thread t = new Thread(FaultyThread);
            t.Start();
            t.Join();
            try
            {
                t = new Thread(BadFaultyThread);
                t.Start();
            }
            catch (Exception ex)
            {
                Console.WriteLine("We won't get here");
            }

            Console.ReadKey();
        }
    }
}
View Code

运行结果:

 

 

 

 

 

 

 

 

 

posted @ 2020-01-14 14:49  智者见智  阅读(334)  评论(0编辑  收藏  举报