雷达智富

首页 > 内容 > 程序笔记 > 正文

程序笔记

.Net各版本多线程使用原理和实践

2024-10-15 30

多线程基本概念

进程:程序在服务器上运行时,占据的计算资源合集,称之为进程。

进程之间不会互相干扰,进程间的通信比较困难(分布式)。

线程:程序执行的最小单位。

线程也包含自己的计算资源,线程是属于进程的,一个进程可以又多个线程。

多线程:一个进程里面,又多个线程并发执行。

多线程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个月前
赞一波!

文章评论

评论问答