多线程之旅(Thread)
在上篇文章中我们已经知道了多线程是什么了,那么它到底可以干嘛呢?这里特别声明一个前面的委托没看的同学可以到上上上篇博文查看,因为多线程要经常使用到委托。源码
一、异步、同步
1.同步(在计算的理解总是要你措不及防,同步当线程做完一件事情之后,才会执行后续动作),同步方法慢,只有一个线程执行,异步方法快,因为多个线程一起干活,但是两者并不是线性增长,当我们的异步线程占有的资源越来越多了,会导致资源可能不够,其次线程过多CPU也是需要管理成本的,所以不是越多越好。
2.异步(可以同时执行多个任务,在同样的时间,执行不同的任务),同步方法卡界面(UI),因为我们的主线程(UI)忙于计算造成了堵塞了。异步方法不卡界面,计算任务交给了子线程完成。winform中体现的玲玲精致。(你品,你细品),web 可以异步的处理一起其他的任务,比如给用户发邮箱(我们的BS结构的,每次访问都是一个子线程,当我们的代码写的比较糟糕,是不是加载比较慢呢哈哈)。异步多线程无序,执行的先后无序,执行的时间不确定,结束也不确定,所以我们很难通过执行时间和先后顺序控制,异步的执行顺序。
二、初识Thread
属性名称 | 说明 |
---|---|
CurrentContext | 获取线程正在其中执行的当前上下文。 |
CurrentThread | 获取当前正在运行的线程。 |
ExecutionContext | 获取一个 ExecutionContext 对象,该对象包含有关当前线程的各种上下文的信息。 |
IsAlive | 获取一个值,该值指示当前线程的执行状态。 |
IsBackground | 获取或设置一个值,该值指示某个线程是否为后台线程。 |
IsThreadPoolThread | 获取一个值,该值指示线程是否属于托管线程池。 |
ManagedThreadId | 获取当前托管线程的唯一标识符。 |
Name | 获取或设置线程的名称。 |
Priority | 获取或设置一个值,该值指示线程的调度优先级。 |
ThreadState | 获取一个值,该值包含当前线程的状态。 |
Thread 中包括了多个方法来控制线程的创建、挂起、停止、销毁,后面的例子中会经常使用。
方法名称 | 说明 |
---|---|
Abort() | 终止本线程。 |
GetDomain() | 返回当前线程正在其中运行的当前域。 |
GetDomainId() | 返回当前线程正在其中运行的当前域Id。 |
Interrupt() | 中断处于 WaitSleepJoin 线程状态的线程。 |
Join() | 已重载。 阻塞调用线程,直到某个线程终止时为止。 |
Resume() | 继续运行已挂起的线程。 |
Start() | 执行本线程。 |
Suspend() | 挂起当前线程,如果当前线程已属于挂起状态则此不起作用 |
Sleep() | 把正在运行的线程挂起一段时间。 |
1.Thread是我们.NET 1.0 给我们提供的多线程类,可以创建,和控制多线程,Thread类构造函数为接受ThreadStart和ParameterizedThreadStart类型的委托参数,下面有请代码神君。
/// <summary> /// 使用Thread 创建多线程 /// </summary> public static void Show() { //实例化创建线程 无参无返回值 Thread thread = new Thread(() => { Console.WriteLine("我是多线程"); }); thread.Start(); //创建5个线程1 for (int i = 0; i < 5; i++) { //这个之所以创建一个k,后面线程不安全会说到 var k = i; //这是一个有参数无返回值多线程 new Thread(x => Running(Convert.ToInt32(x))).Start(k); } Console.Read(); } /// <summary> /// 一个执行需要长时间的任务 /// </summary> static void Running(int s) { Console.WriteLine("**********************************"); Console.WriteLine("执行开始啦" + s); Console.WriteLine("获取当前执行的线程ID:" + Thread.CurrentThread.ManagedThreadId.ToString()); var j = 0; for (int i = 0; i < 1000000000; i++) { j++; } Console.WriteLine("执行结束啦" + s); }
二、渐入佳境
1.运行上面的代码,可以看到线程的无序性,虽然我们的0最先开始执行的,但是不是第一个结束的,这个是因为我们每个线程执行的时间的不确定性。这里也要特别说明为什么Thread构造函数传递的是ThreadStart和ParameterizedThreadStart类型的委托参数,为什么不是Action ,Func,答案就是.NET 1.0的时候还没有Action 、Func。ThreadStart委托是一个无参无返回值上代码中我们创建了,ParameterizedThreadStart委托是一个有参数无返回值,但是我们可以看到我们的参数是一个object类型,是一个不安全的参数(当时泛型也没有出来)当然为了防止这问题,我们也是想到了方法,那就是我们可以通过一个泛型类,帮我们限制参数类型。
/// <summary> /// 防止参数不安全 /// </summary> public static void Show5() { //我们创建一个泛型类,限制我们的类型 MyThread<string> mythread = new MyThread<string>("Thread_child"); //将我们的方法传递,进去 Thread th3 = new Thread(mythread.ThreadChild); //启动线程 th3.Start(); } /// <summary> /// 创建一个泛型类 /// </summary> /// <typeparam name="T"></typeparam> class MyThread<T> { private T data; public MyThread(T data) { this.data = data; } public void ThreadChild() { Console.WriteLine("Child Thread Start! Result:{0}", data); } }
2.我们在上面还提供了其他的方法,但是这些方法已经不建议使用了,现在已经弃用了,因为我们无法精确地控制线程的开启与暂停,当我们将线程挂起的时候,同时也会挂起线程使用的资源,会导致死锁,不建议使用。将线程销毁也不建议 不一定及时/有些动作发出收不回来。(这里我使用的是.net Core 3.1 执行直接报错了哈哈)
/// <summary> /// 使用Thread 线程挂起、唤醒线程、销毁,方式是抛异常、取消Abort异常 /// </summary> public static void Show1() { //创建一个Thread 线程 Thread thread = new Thread(() => { Running(); }); //开启线程 thread.Start(); //这个是线程挂起 //thread.Suspend(); //唤醒线程 //thread.Resume(); //上面的两个方法,现在已经弃用了,因为我们无法精确地控制线程的开启与暂停 //当我们将线程挂起的时候,同时也会挂起线程使用的资源,会导致死锁,不建议使用 try { //将线程销毁 //也不建议 不一定及时/有些动作发出收不回来 thread.Abort(); } catch (Exception) { //静态方法将线程异常取消继续工作 Thread.ResetAbort(); } Console.Read(); }
3.线程优先级,当然我们的线程是一个无序的,也有控制线程执行的权重,但是这个优先级不是绝对的,因为线程的执行顺序还是看我们的CPU爸爸的,但是我们可以利用Priority属性做线程的权重执行,使用也很简单
/// <summary> /// 使用Thread 线程的优先级(但是执行还是看CPU,可以做优先级,但是不是绝对优先) /// </summary> public static void Show3() { //创建一个Thread 线程 Thread thread = new Thread(() => { Running(); }); thread.Start(); //thread.Priority属性可以设置线程的优先级关系 thread.Priority = ThreadPriority.Highest; Console.WriteLine("执行完啦啦啦啦啦啦啦啦啦啦啦拉拉"); Console.Read(); }
4.前台线程、后台线程(这个字面意思,还是和我们的理解是不一样的)我们设置IsBackground控制线程是否(前/后)台线程。默认是前台线程,启动之后一定要完成任务的,阻止进程退出。指定后台线程:随着进程退出。
三、多线程起飞
1、异步回调
1.我们的Thread没有给我提供异步回调的功能,没办法需要自己造轮子了,我们可以先想一下回调的需求是什么,需求分析:当我们的线程任务执行完之后需要之后某些方法。我们细品一下,我们要执行完之后,在执行一个人任务,那就是同步执行异步方法了吧。我们在子线程中怎么同步执行呢?下面的代码就实现了回调功能不管我们执行多少次回调总会在任务后面执行。
/// <summary> /// 异步回调执行 /// </summary> public static void Show6() { //创建一个任务委托 ThreadStart threadStart = () => { Console.WriteLine("我是任务"); }; //创建一个回调执行的委托 Action action = () => { Console.WriteLine("哈哈,我就是你们的回调方法哈,记得双击么么哒"); Console.WriteLine("*********************************************"); }; ThreadWithCallback(threadStart, action); Console.ReadLine(); } /// <summary> /// 回调封装 无返回值 /// </summary> /// <param name="start"></param> /// <param name="callback">回调</param> private static void ThreadWithCallback(ThreadStart start, Action callback) { Thread thread = new Thread(() => { start.Invoke(); callback.Invoke(); }); thread.Start(); }
2、返回参数
1.当然我们使用线程需要返回参数,但是我们的Thread没有给我们提供返回值的委托和方法,这个要莫子搞罗?当然我们先分析需求,我们要获取返回值是不是要等线程执行之后呢?好的线程执行我们可以使用Join堵塞线程等它执行完毕,但是我们要怎么获取返回值呢?对了我们可以创建一个变量,我们的线程给变量赋值吗?
/// <summary> /// 异步返回值 /// </summary> public static void Show7() { //创建一个委托 Func<string> func = () => { return "我是返回值"; }; //获取执行结果 Console.WriteLine(ThreadWithReturn(func).Invoke()); Console.ReadLine(); } /// <summary> /// 有返回值封装(请根据本案例自行封装回调) /// </summary> /// <typeparam name="T">返回值类型</typeparam> /// <param name="func">需要子线程执行的方法</param> /// <returns></returns> private static Func<T> ThreadWithReturn<T>(Func<T> func) { //初始化一个泛型,限制我们的类型 T t = default(T); ThreadStart newStart = () => { //线程给变量赋值 t = func.Invoke(); }; //创建线程 Thread thread = new Thread(newStart); //执行线程 thread.Start(); //创建一个委托 无参有返回值,执行委托会发生执行线程等待堵塞 //当线程执行完之后,也就是说线程已经给变量t赋值了,我们就返回t return new Func<T>(() => { thread.Join(); return t; }); }
四、Thread总结
1.大家是不是觉得多线程很酷呢?哈哈我刚刚学的时候也是激动的心颤抖的手。当然文章中我们介绍了很多API的使用,大家可以动手试试,API的使用是小事,最重要的是我们的思路,到我们看到回调封装和返回值封装,我们都是利用了多线程的一些特性,来完成的这些功能拓展的。我们宏观的看多线程感觉很恐怖,但是在我们做回调函数的时候是不是感觉有一种微观看法,线程执行的内部也是同步的执行哪些方法的。好了今天就写到这里昨天晚上9点多就睡了,早起撸个文章美滋滋。当然多线程还有讲完的,才说道了.NET 1.0哈哈,后续的文章也会写出来。
第一次,当它本可进取时,却故作谦卑;
第二次,当它空虚时,用爱欲来填充;
第三次,在困难和容易之间,它选择了容易;
第四次,它犯了错,却借由别人也会犯错来宽慰自己;
第五次,它自由软弱,却把它认为是生命的坚韧;
第六次,当它鄙夷一张丑恶的嘴脸时,却不知那正是自己面具中的一副;
第七次,它侧身于生活的污泥中虽不甘心,却又畏首畏尾。