.Net各版本多线程使用原理和实践
多线程基本概念
进程:程序在服务器上运行时,占据的计算资源合集,称之为进程。
进程之间不会互相干扰,进程间的通信比较困难(分布式)。
线程:程序执行的最小单位。
线程也包含自己的计算资源,线程是属于进程的,一个进程可以又多个线程。
多线程:一个进程里面,又多个线程并发执行。
多线程Thread:类,就是一个封装,是.netFramework对线程对象的抽象封装
通过Thread去完成的操作,最终是通过向操作系统请求得到的执行流
CurrentThread:当前线程,任何操作执行都是线程完成的,运行当前这句话的线程
ManagedThreadId:是.Net平台给Thread起的名字,就是个int值,尽量不重复。
任何的异步多线程都离不开委托delegate,多线程写法不难,但是用好很难,多年老司机也经常掉坑里。以Winform开发来讲,同步单线程方法主UI线程忙于计算不能响应就会卡界面,异步多线程方法不卡界面,计算任务交给了子线程,主UI线程已经闲置,可以响应别的操作。
多线程是用资源换性能,但并不是线性增长。因为多线程有协调管理成本,资源也有上限(例如把线程比作车,虽然有50辆车,但是只有3根道,那想快也快不起来)。
多线程的特点:多线程无序性不可预测性(启动无序,结束无序)
原因:因为线程是操作系统资源,CLR只能去申请,具体是什么顺序无法控制。执行事件不确定,同一个线程同一个任务耗时可能也不同
跟操作系统的调度策略有关,CPU分片(计算能力太强,1s拆分1000份,宏观上就变成并发的)。线程的优先级可以影响操作系统的调度。
解决多线程不可预测性的方法?控制顺序
AsyncCallback 异步回调
AsyncCallback asyncCallback = (ar) => {
//do something
};
Action action = () => { };
action.BeginInvoke(asyncCallback, null);
IsCompleted等待
IsCompleted是一个属性,用来描述异步动作是否完成,其实一开始就是个false,异步动作完成后回去修改这个属性为true
信号量
AsyncResult.AsyncWaitHandle.WaitOne();//阻塞当前线程,直到收到信号量,无延迟
AsyncResult.AsyncWaitHandle.WaitOne(-1)一直等待直到成功
AsyncResult.AsyncWaitHandle.WaitOne(1000)等待且最多只等待1000ms超过就继续执行,用来做超时控制。
IAsyncResult异步调用结果,描述异步操作的。
func.EndInvoke(asyncResult)可以获取异步调用的真是返回值。每个异步操作,只能调用一次EndInvoke,多个会异常。
.Net多线程代码如何编写?
.NETFramework有N多个版本,就有N多个多线程的使用方式。
.NetFramework1.0 1.1 Thread
Thread的api特别丰富,但是其实都用不好,因为线程资源是操作系统管理的,相应并不灵敏,没那么好控制。Thread启动线程是没有控制的,可能导致死机。(循环创建无上限控制)
ThreadStart ts = () => {
Console.WriteLine($"ThreadId-{Thread.CurrentThread.ManagedThreadId} " + "ThreadStart开始运行了");
Thread.Sleep(3000);
Console.WriteLine($"ThreadId-{Thread.CurrentThread.ManagedThreadId} " + "ThreadStart结束运行了");
};
Thread thread = new Thread(ts);
thread.Start();
.NetFramework2.0 ThreadPool(新的CLR)非常大的版本变化
ThreadPool线程池,池化资源管理设计思想。线程是一种资源,之前每次要用就去申请一个线程,使用完之后释放掉。池化就是做一个容器,提前申请N个线程,程序需要使用线程直接找容器获取,用完后再放回容器(控制状态标记线程是否再使用),避免频繁申请和销毁。容器自己会根据闲置的数量去申请和释放。后面的写法都是从线程池拿的。
优点:1线程服用2限制最大线程数量
缺点:Api太少了,线程等待顺序控制特别弱,影响了实战。
WaitCallback callback = o => {
Console.WriteLine($"ThreadId-{Thread.CurrentThread.ManagedThreadId} " + "ThreadPoolStart开始运行了");
Thread.Sleep(3000);
Console.WriteLine($"ThreadId-{Thread.CurrentThread.ManagedThreadId} " + "ThreadPoolStart结束运行了");
};
ThreadPool.QueueUserWorkItem(callback);
.NETFramework3.0 Task
Task被称为多线程的最佳实践。优点:1全部是线程池线程。2提供了丰富的Api,非常适合开发实践。
下面代码列举了几个常用的API基本可以满足日常业务对执行顺序的控制。
List<Task> tasks = new List<Task>();
Action action = () => {
Console.WriteLine($"ThreadId-{Thread.CurrentThread.ManagedThreadId} " + "Task1开始运行了");
Thread.Sleep(3000);
Console.WriteLine($"ThreadId-{Thread.CurrentThread.ManagedThreadId} " + "Task1结束运行了");
};
Task task = new Task(action);
task.Start();
var task1 = Task.Run(() =>
{
Console.WriteLine($"ThreadId-{Thread.CurrentThread.ManagedThreadId} " + "Task2开始运行了");
Thread.Sleep(3000);
Console.WriteLine($"ThreadId-{Thread.CurrentThread.ManagedThreadId} " + "Task2结束运行了");
});
tasks.Add(task);
tasks.Add(task1);
//Task.WaitAny(tasks.ToArray());//等待任一线程结束后执行后面代码,会阻塞当前线程
//Task.WaitAll(tasks.ToArray());//等待线程全部结束后执行后面代码,会阻塞当前线程
//下面方法不会阻塞当前线程
//Continue的后续线程,可能是新线程,也可能是刚完成任务的线程,还可能是同一个线程,不可能是主线程
TaskFactory taskFactory = new TaskFactory();
taskFactory.ContinueWhenAny(tasks.ToArray(), (one) => {
Console.WriteLine($"ThreadId-{Thread.CurrentThread.ManagedThreadId} " + $"任务{one.Id}第一个完成了");
});
taskFactory.ContinueWhenAll(tasks.ToArray(), (list) =>
{
var ids = list.Select(x => x.Id).ToArray();
Console.WriteLine($"ThreadId-{Thread.CurrentThread.ManagedThreadId} " + $"所有任务{string.Join(",", ids)}全部完成了");
});
await/async
成对出现语法糖,简化了多线程开发,只要将普通方法加上async就变成了可以异步的方法了。
调用异步方法时,在方法前面加上await就可以等待异步方法执行完成后再执行后面的代码。
委托BeginInvoke()
这个方法在.NetCore已经取消了。
多线程应用场景
有严格实践限制的,先后顺序的,只能单线程。
用多线程是为了提升效率,各个任务可以独立并发执行。例如一个操作要查询数据库,要调用接口,要读硬盘文件,这些操作可以多线程操作,因为任务彼此不干扰。
既需要多线程来提升性能,又需要再多线程全部完成后才能执行的操作的做法
Task.WaitAll();//等待线程全部结束后执行后面代码,会阻塞当前线程,直到全部任务结束。
Task.WaitAny()//阻塞当前线程,直到任一任务结束
不阻塞当前线程的写法
TaskFactory taskFactory = new TaskFactory();
taskFactory.ContinueWhenAll()//创建一个延续任务,该任务再一组指定的任务完成后开始
taskFactory.ContinueWhenAny()//创建一个延续任务,该任务再一组指定的任务中的任一一个完成后开始
Continue的后续线程,可能是新线程,也可能是刚完成任务的线程,还可能是同一个线程,不可能是主线程
如果同时写了Continue和Wait,任务全部完成之后,但是谁前谁后无法掌控。现有的四个方法已经能够控制90%的多线程业务场景了,需要灵活应用。
多线程准则
1尽量不要线程套线程,有更优秀的方法
2子线程是不能操作界面的
更新于:1个月前相关文章
- 【说站】java多线程应用场景
- 【说站】java多线程原理
- 【说站】java多线程有几种实现方法
- 【说站】python thread模块如何实现多线程
- 【说站】Thread在java中生成接口
- 【说站】java Thread的状态分析
- 【说站】java中Thread Dump是什么
- C# 使用Barrier进行多线程同步
- .NET中Task和ValueTask的区别
- C#中异步编程时使用Task.Factory.StartNew和Task.Run的区别
- .Net多线程下载断点续传开源库Downloader用法
- Windows多线程编程之使用Windows API CreateThread创建一个线程
- Thread类及其对象——Python 多线程
- 计时器对象——Python 多线程
- 解决Python多线程并发安全性问题
- Java开发者实战:高效解决多线程并发控制难题