zoukankan      html  css  js  c++  java
  • C#异步编程:多线程基础Thread类

    Thrad类提供了:在不同线程上执行方法的能力

    Thread类位于System.Threading名称空间下,学会使用一下几点技能,便可基本掌握最简单的多线程操作

    知识点1:创建并启动线程

    class Program
    {
        static void Main(string[] args)
        {
            Thread thread1 = new Thread(new ThreadStart(CommonService.GetConnection));
            Console.WriteLine("thread1 is ready to running....");
            thread1.Start();
    
            Console.ReadLine();
        }
    }
    
    class CommonService
    {
        public static void GetConnection()
        {
            Console.WriteLine("connect success....");
        }
    }
    

    说明:1、new一个Thread对象,便创建了一个新的线程;2、调用Start方法,将创建的线程的状态设置为running状态;操作系统会将该线程视为执行,然后寻找空闲时间,在该线程上执行GetConnection方法。

    知识点2(不常用):暂停(挂起)某个线程一段时间

    class Program
    {
        static void Main(string[] args)
        {
            Thread.Sleep(new TimeSpan(0,0,5));//将当前线程暂停五秒(暂停的是,Main方法所在的主线程)
            Thread thread1 = new Thread(new ThreadStart(CommonService.GetConnection));
            Console.WriteLine("thread1 is ready to running....");
            thread1.Start();
    
            Console.ReadLine();
        }
    }
    
    class CommonService
    {
        public static void GetConnection()
        {
            Thread.Sleep(TimeSpan.FromSeconds(3));//暂停的是GetConnection方法所在的线程
            Console.WriteLine("connect success....");
        }
    }
    

    说明:使用Thread类的静态方法--Sleep(),系统会自动判断执行的代码所处在哪一个线程"。1、在主线程中使用了Sleep,那么整个应用会停留在该位置"假死"到指定的时间,才继续向下执行;2、在子线程中使用Sleep,那么子线程会暂停操作,到指定时间才继续执行。开发工作中,不建议使用Sleep()方法。

    知识点3(不常用):在一个线程中等待另个线程执行完成再继续

    当子线程创建并启动后,主线程同一时刻也在运行;子线程何时执行完毕是不确定的;如果我们希望等待某个线程执行完后,才继续执行另一个线程的代码,就需要使用Join()方法。

    class Program
    {
        static void Main(string[] args)
        {
            Thread.Sleep(new TimeSpan(0,0,5));//将Main方法所在的主线程暂停五秒
            Thread thread1 = new Thread(new ThreadStart(CommonService.GetConnection));
            Console.WriteLine("thread1 is ready to running....");
            thread1.Start();
            thread1.Join();//join方法有两个重载,这里使用无参数形式的Join()方法来等待线程执行结束。
            Console.WriteLine("main is ready to completed ....");
            Console.ReadLine();
        }
    }
    
    class CommonService
    {
        public static void GetConnection()
        {
            Thread.Sleep(TimeSpan.FromSeconds(3));//暂停的是GetConnection方法所在的线程
            Console.WriteLine("connect success....");
        }
    }
    

    说明:从执行结果可以看出,使用Join()方法后,线程之间便存在了一种同步关系--即:你先干完,我再干。

    知识点4:前台线程与后台线程

    class Program
    {
        static void Main(string[] args)
        {
            Thread thread = new Thread(new ThreadStart(CommonService.GetConnection));
            /**
             * 可以通过解除下面注释来观察前台线程与后台线程的区别:
             * 未解除注释--Main方法所在的主线程率先退出;GetConnection方法所在的子线程是前台线程,应用程序会等待子线程中的代码执行完毕后才结束进程。
             * 解除注释--Main方法所在的主线程率先退出;GetConnection方法所在的子线程是后台线程,应用程序检测进程中没有正在执行的前台线程后,直接结束进程。
             */
            //thread.IsBackground = true;
            thread.Start();
        }
    }
    
    class CommonService
    {
        public static void GetConnection()
        {
            Console.WriteLine($"当前正在执行方法所在的线程是否为后台线程:{Thread.CurrentThread.IsBackground}");
            Thread.Sleep(TimeSpan.FromSeconds(3));
            Console.WriteLine("connect success....");
        }
    }
    

    以控制台程序为例:1、手动创建的线程,默认也是一个"前台线程";3、如果指定IsBackground=true,那么线程被指定为后台线程;4、应用程序会等待所有的前台线程执行完毕后再结束工作(如果程序中只剩下后台线程的话,应用程序会直接结束工作)

    知识点5:给线程传递参数

    使用Thread对象的Start方法的重载形式:通过给Start()方法传递参数,达到给线程传递参数

    错误内容:System.InvalidOperationException:“该线程是用不接受参数的 ThreadStart 委托创建的。ThreadStart不接受参数,所以需要一个能够接收参数的委托:ParameterizedThreadStart

    方式1:使用ParameterizedThreadStart创建线程、并通过给Start()方法传递参数,来给线程传递参数

    private static void TestThreadBase()
    {
        Thread thread = new Thread(new ParameterizedThreadStart(CommonService.ConnectionWithTarget));
        thread.Start(12);
    }
    class CommonService
    {
        public static void ConnectionWithTarget(object targetName)
        {
            Console.WriteLine($"线程 {targetName} 连接成功....");
            Thread.Sleep(TimeSpan.FromSeconds(5));
            Console.WriteLine($"线程 {targetName} 退出 ...");
        }
    }
    /*
    线程 12 连接成功....
    线程 12 退出 ...
    */
    

    使用ParameterizedThreadStart委托的方式给线程传递参数,那么执行线程的方法的参数列表就需要和ParameterizedThreadStart委托的参数列表匹配--即:需要改方法定义一个Object类型的形参;假如我希望执行线程的方法的参数是Student类型的(如下),那么使用ParameterizedThreadStart委托,还能奏效么?
    准备一个入参为Student类型的方法

    class CommonService
    {
        public static void ShowTargetInfo(Student student) 
        {
            Console.WriteLine($"{student.StudentName}的性别是{student.StudentSex},身高是{student.StudentHeight}");
        }
    }
    class Student
    {
        public int StudentId { get; set; }
        public string StudentName { get; set; }
        public double StudentHeight { get; set; }
        public char StudentSex { get; set; }
    }
    

    尝试给ParameterizedThreadStart委托添加ShowTargetInfo方法

    上面的报错告诉我们,ShowTargetInfo方法不匹配ParameterizedThreadStart委托,所以我们需要转换思路:使用Lambda表达式来实例化ParameterizedThreadStart委托;然后再在Lambda表达式中调用ShowTargetInfo方法,这样ShowTargetInfo也算是在该线程上运行了。

    方式2:使用Lambda表达式实例化委托类型,然后在Lambda表达式中调用方法,达到给线程传递参数的目的

    private static void TestThreadBase()
    {
        Student student = new Student() { StudentId = 11, StudentName = "小哇", StudentSex = '男', StudentHeight = 170.5 };
        //方式1
        Thread thread1 = new Thread(new ThreadStart(()=> 
        {
            Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}");
            CommonService.ShowTargetInfo(student);
        }));
        thread1.Start();
    
        //方式2
        Thread thread2 = new Thread(new ParameterizedThreadStart(item=> 
        {
            Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}");
            CommonService.ShowTargetInfo(student);
        }));
        thread1.Start();
    
        //方式3
        Thread thread3 = new Thread(new ParameterizedThreadStart(item =>
        {
            var stu = item as Student;
            Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}");
            CommonService.ShowTargetInfo(stu);
        }));
        thread3.Start(student);
    
        //方式1 简写形式 Lambda表达式隐式创建了ThreadStart委托(也是我最常用的给线程传递参数的形式)
        Thread thread4 = new Thread(()=> 
        {
            Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}");
            CommonService.ShowTargetInfo(student);
        });
        thread4.Start();
    
        //方式2 简写形式 Lambda表达式隐式创建了ParameterizedThreadStart委托
        Thread thread5 = new Thread(item =>
        {
            Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}");
            CommonService.ShowTargetInfo(student);
        });
        thread5.Start();
    
        //方式3 简写形式 Lambda表达式隐式创建了ParameterizedThreadStart委托
        Thread thread6 = new Thread(item =>
        {
            var stu = item as Student;
            Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}");
            CommonService.ShowTargetInfo(stu);
        });
        thread5.Start(student);
    }
    

    向线程传递参数,本质上是向线程所执行的方法传入实参;利用Lambda表达式调用目标方法的形式,使目标方法参数列表的灵活度高,不受限制。

    知识点6:在多线程中处理异常

    当我们创建了一个线程并启动该线程,那么在该线程中一旦发生了异常,会发生什么后果呢?

    在工作线程中(即:非主线程)发生了异常,会导致程序崩溃

    private static void TestThreadException() 
    {
        new Thread(()=> 
        {
            Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} is running .....");
            Thread.Sleep(3000);
            throw new Exception($"从线程{Thread.CurrentThread.ManagedThreadId}中抛出异常....");
        }).Start();
    }
    static void Main(string[] args)
    {
        TestThreadException();
        Console.ReadLine();
    }
    

    分析:Lambda表达式中的代码片段是运行在子线程中的,三秒后该线程中抛出异常;由于没有处理该异常控制台程序直接崩溃。

    在子线程的代码外部试图捕获异常也是徒劳的

    static void Main(string[] args)
    {
        try
        {
            TestThreadException();
        }
        catch (Exception ex)
        {
            Console.WriteLine("捕捉到了子线程的异常{0}", ex.Message);
        }
        Console.ReadLine();
    } 
    

    分析:本来我们以为在Main()方法中使用try..catch块儿来试图捕获子线程中的异常,但实际却并未能捕捉到该异常,程序照常崩溃

    在子线程中处理异常,才是多线程中处理异常的正确方式

    private static void TestThreadException()
    {
        new Thread(() =>
        {
            try
            {
                Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} is running .....");
                Thread.Sleep(3000);
                throw new Exception($"从线程{Thread.CurrentThread.ManagedThreadId}中抛出异常....");
            }
            catch (Exception ex)
            {
                Console.WriteLine("捕捉到了子线程的异常{0}", ex.Message);
            }
        }).Start();
    }
    /*
    3 is running .....
    捕捉到了子线程的异常从线程3中抛出异常....
    */
    

    多线程编程处理异常:不要在线程中抛出异常,而是使用try..catch块儿。

    以上便是对多线程基础的知识点的总结,记录下来以便以后查阅。

  • 相关阅读:
    STM32 定时器用于外部脉冲计数
    幸福是怎么来的
    STM32 I/O的耐压问题,中断问题,以及如何在Keil (RVMDK) 中观察程序的执行时间
    STM32输入捕获简介
    STM32 直流减速电机控制
    2013 初始
    js 为字符串添加样式
    js 实现页面显示钟表
    JavaScript 入门总结
    ADO.NET基本数据库编程
  • 原文地址:https://www.cnblogs.com/bigbosscyb/p/14132228.html
Copyright © 2011-2022 走看看