zoukankan      html  css  js  c++  java
  • 进程和线程(线程是轻量级进程)(中)

    创建线程

    线程是通过扩展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

    运行结果:

     

     

     

     

     

     

     

     

  • 相关阅读:
    如何让create-react-app锦上添花,满足实际需求?
    《漫画算法》笔记-下篇
    《漫画算法》笔记-上篇
    react + typescript 学习
    node http 模块 常用知识点记录
    vue 相关技术文章集锦
    读后感:数据结构与算法JavaScript描述
    css 揭秘-读书笔记
    vue-textarea 自适应高度
    消除浏览器对input的自动填充
  • 原文地址:https://www.cnblogs.com/zhaoyl9/p/12191973.html
Copyright © 2011-2022 走看看