进程是一个应用程序运行的实例,程序在服务器运行时占据全部计算资源总和,是一个计算机概念
进程在响应操作时最小单位,也包含CPU,内存,网络,硬盘,IO,也是一个计算机概念。一个进程包含多个线程,线程属于进程,进程销毁线程也就没了
Thread类是C#语言对线程对象的一个封装
同步
发起调用,完成后才继续,非常符合开发思维,有序执行
异步
发起调用,不等待完成,直接进入下一行执行,启动一个线程来完成方法的计算
同步方法卡界面:主线程(UI线程)忙于计算,无暇他顾
异步多线程方法不卡界面:主线程闲置,计算任务交给子线程完成,改善用户体验,winform点击按钮不至于卡死
web应用发个短信通知,使用异步多线程完成发短信的操作
同步方法慢,只有一个线程计算
异步多线程方法块,因为多个线程并发计算
多线程其实是资源换性能。①资源不是无限的;②资源调度损耗
一个订单表统计很耗时间,能不能多线程优化下性能? 答案是不能的,这就是一个操作,没法并行
需要查询数据库/调度接口/读硬盘文件/做数据计算,能不能多线程优化下性能。这个答案是可以的。多个任务是可以并行的
线程不是越多越好,因为资源有限,而且调度有损耗
同步方法由于进行,异步多线程无序
启动无序:线程资源是向操作系统申请的,有操作系统的调度策略决定,所以启动顺序随机
同一任务同一个线程,执行时间也不确定,因为CPU的分片
以上结论所以结果也是无序的
使用多线程请一定小心,很多事不是想当然的,尤其是多线程操作间有顺序要求的时候,通过延迟一点启动来控制顺序?或者预计下结束顺序?这些都不靠谱
.NET Framework 1.0出现
{
ThreadStart method = ()=>{this.DoSomething()};
Thread thread = new Thread(method);
thread.Start();//开启线程
thread.Suspend();//暂停(弃用)
thread.Resume();//恢复(弃用) 真的不该要的,暂停一下不一定马上暂停;让线程操作太复杂了
thread.Abort();//线程销毁
Thread.ResetAbort();//恢复销毁的线程
thread.Join();//等待线程
thread.Join(1000);//最多等待1000ms
thread.Priority=ThreadPriority.Higheset;//最高优先级;优先执行,但不代表优先完成,甚至极端情况下,还有意外发生,不通过这个来控制线程的执行顺序
thread.IsBackgroud=false;//是否后台线程;默认是false 前台线程。进程关闭,线程需要计算完成后才退出。
}
{
//基于thread封装一个回调
//回调:启动子线程执行动作A--不阻塞--A执行完后子线程执行动作B
private viod ThreadWithCallBack(TreadStart threadStart,Action actionCallback)
{
ThreadStart method = new ThreadStart(()=>{
threadStart();
actionCallback();
})
new Thread(method).Start();
}
}
{
//基于thread封装一个异步、非阻塞、还能获取返回值的的方法
private Func<T> ThreadWithReturn<T>(Func<T> func)
{
T t= default(T);
ThradStart threadStart = new ThreadStart(()=>{
t = func();
})
Thread thread = new Thread(threadStart);
thread.Start();
return new Func<T>(()=>{
thread,Join();
return t;
});
}
}
Thread功能繁多,反而用不好–就像给4岁小孩一把热武器,反而会造成更大的伤害
对线程数量没有管控
线程池 .NET Freamwork 2.0出现
如果某个对象创建和销毁代价比较高,同时这个对象还可以反复使用的,就需要一个池子保存多个这样的对象,需要用的时候从长池子里面获取;用完之后不用销毁,放回池子
节约资源提升性能,此外还能管控总数量,防止滥用
线程池都是后台线程
{
ThreadPool.QueueUserWorkItem(o=>{this.DoSomething()});//不带参数
ThreadPool.QueueUserWorkItem(o=>{this.DoSomething(o)},"xxx");//不带参数
ThreadPool.GetMaxThreads(out int workerThreads,out int completionPortThreads);//当前电脑最大线程数/当前电脑最大异步/IO线程数
ThreadPool.GetMinThreads(out int workerThreads,out int completionPortThreads);//当前电脑最小线程数/当前电脑最小异步/IO线程数
//设置的线程池数据量是全局的
//委托异步调用-Task-Parrallel-async/await 全部都是线程池的线程
//直接new Thread不受这个数量的限制的(但是会占用线程池的线程数量)
ThreadPool.SetMaxThreads(12,12);//设置最大线程数
ThreadPool.SetMinThreads(2,2);//设置最小线程数
//等待
ManualResetEvent mre = new ManualResetEvent(false);
//false -- 关闭--Set打开--true--WaitOne就能通过
//true -- 打开--ReSet关闭--false--WaitOne就只能等待
ThreadPool.QueueUserWorkItem(o=>{this.DoSomething();mre.Set();});
mre.WaitOne();
}
Task是 .NET Freamwork 3.0出现的,线程是基于线程池,提供了丰富的API
Task线程启动方式:
{
//方法一
Task task = new Task(()=>this.DoSomething());
task.Start();
//方法二
Task task =Task.Run(()=>this.DoSomething());
//方法三
TaskFactory taskFactory = Task.Factory;
Task task=taskFactory.StartNew(()=>this.DoSomething());
//方法四,带返回值
Task<int> res = Task.Run<int>(()=>{return 2});
int i = res.Result;//会阻塞
}
Task常用API:
Thread.Sleep(2000);//同步等待,阻塞当前线程
Task.Delay(2000);//异步等待,不阻塞当前线程
//不阻塞当前线程等待2000ms,执行ContinueWith里面的委托
Task.Delay(2000).ContinueWith(t=>{
this.DoSomething();
});
//等待
List<Task> taskList= new List<Task>();
TaskFactory taskFactory = Task.Factory;
Task.WaitAll(taskList.toArray());//方法一:等待全部完成
Task.WaitAny(taskList.toArray());//方法二:等待任意一个任务完成
//回调
taskFactory.ContinueWhenAll(taskList.toArray(),t=>{this.DoSomething();});//一组任务完成时调用回调方法
taskFactory.ContinueWhenAny(taskList.toArray(),t=>{this.DoSomething();});//一组任务完成y一个时调用回调方法
taskFactory.ContinueWith(t=>{this.DoSomething()});//单个任务回调
//不使用线程池控制线程任务数量,10000个任务,20个线程数量
List<Task> taskList= new List<Task>();
for(int i=0;i<10000,i++){
if(taskList.Count(t=>t.Status!=TaskStatus.RanToCompletion)>20){
//等待任务完成
Task.WaitAny(taskList.toArray());
//清理已完成的任务
taskList = taskList.where(t=>t.Status!=TaskStatus,RanToCompletion).toList();
}
taskList.Add(Task.Run(t=>{
this.DoSomething()
}));
}
什么时候能要多线程?
在任务能并发的时候
多线程能干嘛?
提升速度/优化用户体验
Parallel是 .NET Freamwork 4.0出现的
Parallel并发执行多个Action 多线程的
主线程会参与计算–所以会阻塞界面
等于TaskWaitAll + 主线程计算
//方法一
Parallel.Invoke(()=>this.DoSomething(),
()=>this.DoSomething(),
()=>this.DoSomething(),
()=>this.DoSomething());
//方法二
Parallel.For(0,5,i=>this.DoSomething());
//方法三
Parallel.ForEach(new int[]{0,1,2,3,4},i=>this.DoSomething());
//方法四
ParallelOptions options = new ParallelOptions();
options.MaxDegreeOfParallelism=3;//指定最大任务数
Parallel.For(0,15,i=>this.DoSomething());
//Parallel是阻塞的,有没有办法不阻塞?包一层!
Task.Run(()=>{
Parallel.For(0,5,i=>this.DoSomething())
});
多线程里面抛出的异常,会终结当前线程,但是不会影响别的线程
那线程去哪里了呢?当然是被吞了。
假如想捕获异常信息,或者还要通知别的线程该怎么办呢
List<Task> taskList= new List<Task>();
try
{
for(int i=0;i<100,i++){
string name = i.toString();
taskList.Add(Task.Run(()=>{
if(name.Equals("3")){
throw new Execption("3异常");
}
else if(name.Equals("11")){
throw new Execption("11异常");
}
else if(name.Equals("55")){
throw new Execption("55异常");
}
Console.WriteLine(name);
}));
}
Task.WaitAll(taskList.toArray());
}
//捕获多线程异常
catch(AggregateExecption aex ){
foreach(var execption in aex.InnerExecptions){
Console.WriteLine(execption.Message);
}
}
catch(Execption ex){
Console.WriteLine(ex.Message);
}
线程异常后经常是需要通知别的线程,而不是等到WaitAll,问题就是要取消多线程
工作中常规建议:多线程的委托里面不允许出现异常,通常在多线程的委托里面包一层try-catch,然后记录下来异常信息,完成需要的操作
List<Task> taskList= new List<Task>();
for(int i=0;i<100,i++){
string name = i.toString();
taskList.Add(Task.Run(()=>{
try{
if(name.Equals("3")){
throw new Execption("3异常");
}
else if(name.Equals("11")){
throw new Execption("11异常");
}
else if(name.Equals("55")){
throw new Execption("55异常");
}
Console.WriteLine(name);
}
cathc(Execption ex){
Console.WriteLine(ex.Message);
}
}));
}
多线程并发任务,某个失败后,希望通知别的线程,都停下来
在Thread时代的时候,使用Thread.Abort()–终止线程,向当前线程抛出一个异常然后终结任务;线程属于OS资源,可能不会立即停下来
Task不能外部终止任务,只能自己终结自己
CancellationTokenSource.Token能让没有启动的任务取消
List<Task> taskList= new List<Task>();
//1.准备cts
CancellationTokenSource cts = new CancellationTokenSource();
for(int i=0;i<10,i++){
string name = i.toString();
taskList.Add(Task.Run(()=>{
//2.准备try-catch
try{
if(!cts.IsCancellationRequested)
Console.WriteLine($"{name} 开始");
Thread.Sleep(5000);
if(name.Equals("3")){
throw new Execption("3异常");
}
else if(name.Equals("11")){
throw new Execption("11异常");
}
else if(name.Equals("55")){
throw new Execption("55异常");
}
//3. Action判断IsCancellationRequested尽快停止,肯定有延迟,在判断环境才会借宿
if(cts.IsCancellationRequested){
Console.WriteLine($"{name} 结束");
}
else{
Console.WriteLine($"{name} 被取消");
}
}
cathc(Execption ex){
Console.WriteLine(ex.Message);
cts.Cancel();
}
},cts.Token));//cts.Token在线程出现异常的时候可以让没有启动任务取消
}
Task.WaitAll(taskList.toArray());
临时变量问题,线程是非阻塞的,延迟启动的;线程执行的时候 i 已经是5了
k是闭包里面的变量,每次循环都有一个独立的k
5个k变量,一个 i 变量
for(int i=0;i<5,i++){
int k=i;
Task.Run(()=>{
Console.WriteLine($"i:{i},k:{k}");
})
}
线程安全:如果你的代码在进程中有多个线程同时运行这一段,如果每个运行的结果都跟单线程运行时的结果一直,那么就线程安全的
线程安全问题一般都是全局变量/共享变量/静态变量/硬盘文件/数据库数据,只要多线程同时访问和修改造成的
lock
lock是语法糖,Monitor.Enter,占据一个引用,别的线程就只能等着
推荐锁是private static readonly object
A 不能是 null,可以编译但是不能运行
B lock(this),在外面如果也要用实例,就冲突了,不推荐
C 不应该是string; string在内存分配上是重用的,会冲突
D lock里面的代码不要太多,lock里面是单线程的
线程安全集合
System.Collections.Concurrent.ConcurrentQueue
数据分拆,避免多线程操作同一个数据,又安全有高效
await/async 是 .NET Framework 4.5出现的,是一个语法糖
await/async语法和使用;
await/async 是c#的保留关键字,通常成对出现;
async修饰方法,可以单独出现,但是有警告;
await在方法体,只能出现在task/async方法前面,只有await会报错;
主线程调用await/async方法,主线程遇到await返回执行后续动作,await后面的代码会等着task任务的完成后再继续执行;
其实就是把await后面的代码包装成一个continueWith的回调动作;
然后这个回调动作可能是Task线程,也可能是主线线程,还可能是新的子线程;
一个async方法,如果没有返回值,可以方法声明返回Task;
await/async能够用同步的方式编写代码,但又是非阻塞的;
原理探究和使用建议
async方法在编译后会生成一个状态机(实现了IAsyncStateMachine接口)
① async方法里面的逻辑其实都放在MoveNext里面
② 主线程new一个状态机,初始化状态为-1
③ 主线程调用MoveNext,来执行await之前的逻辑
④ 主线程将状态改为0,再启动一个Task任务,继续回调用方法的地方执行后续代码
⑤ 子线程调用MoveNext,将状态改为-1,再去执行await之后的代码