1.多线程
1.1进程:一个程序的运行实例,就是一个进程, 而一个进程又是由多个线程所组成的。
1.2线程:线程是程序中的一个执行流,每个线程都有自己的专有寄存器(栈指针、程序计数器等),但代码区是共享的,即不同的线程可以执行同样的函数
1.3句柄: 是Windows系统中对象或实例的标识,这些对象包括模块、应用程序实例、窗口、控制、位图、GDI对象、资源、文件等
1.4多线程:多线程是指程序中包含多个执行流,一般是并行状态。
Thread是.Net最早的多线程处理方式,它出现在.Net1.0时代,虽然现在已逐渐被微软所抛弃,微软强烈推荐使用Task,但从多线程完整性的角度上来说,我们有必要了解下早期多线程的是怎么处理的,以便体会.Net体系中多线程处理方式的进化.
1.4.1Thread类构造函数重载,
public delegate void ThreadStart()
一个参数ThreadStart,这是一个无参无返回值委托类型,通过其Start()方法启动线程.
public delegate void ParameterizedThreadStart(object obj);
一个参数ParameterizedThreadStart,这是含一个参数无返回值的委托类型.通过委托对象的Start(object obj)方法启动线程.
如果想传入多个参数怎么办,定义一个结构体,里面定义多个代传入的变量,然后将结构体变量传递给委托参数即可.
我们也可以自定义一个类型,传递给参数object;也可以自定义一个类,将传入委托的方法写在这个类中,这个方法可以是静态方法,也可以是静态方法,具体看项目开发需求.
如下代码示例展示了如何用Thread类创建并运行多线程并行任务:
static void Main(string[] args)
{
//定义工作线程, 线程状态:Unstarted
Thread workthread = new Thread(WorkMethod);
//线程分前台线程和后台线程,前台线程即主线程,后台线程即工作线程,用户线程或服务线程,低优先级别。
//通过将工作线程设置成后台线程,main()主程序运行结束后,则工作线程也自动退出,否则主程序会一直等待工作线程结束后才会退出.
workthread.IsBackground = true;
//启动工作线程, 线程状态Running
workthread.Start("ParamFromMainThread");
//线程的其它状态改变方法
//workthread.Abort(); //线程状态:Stopped,用于永久地停止也即销毁托管线程。
//workthread.Suspend(); //线程状态:Suspended
//workthread.Resume(); //线程状态:Running
//workthread.Interrupt(); //中断线程,状态:Interrupted
while (!workthread.IsAlive);//loop unitil the work thread is activated
//工作线程的join方法会阻塞所有其他进程,包括主线程,直到当前工作线程执行结束才停止出阻塞
//workthread.Join();
//main()主线程
for (int i = 0; i <= 5; i++)
{
Thread.Sleep(200);//模拟主线程的操作
Console.WriteLine("执行主线程" + (i / 5.0).ToString("p"));
}
Console.WriteLine("主线程结束");
}
private volatile bool _isstop;
static void WorkMethod(object obj)
{
try
{
string param = (string)obj;
for (int i = 0; i <= 20; i++)
{
Thread.Sleep(200);//模拟工作线程的操作
Console.WriteLine("执行工作线程" + (i / 5.0).ToString("p"));
}
}
catch(Exception ex)
{
Console.WriteLine("ThreadError");
}
finally
{ Console.WriteLine("工作线程结束"); }
}
2.后台线程:
Thread类创建的线程默认是前台线程(线程池(ThreadPool)创建的线程是后台线程).
Thread创建的线程通过Isbackground属性设置为后台线程,如果主线程终止了,则后台线程自动终止.
线程分前台线程和后台线程,前台线程即主线程,后台线程理解为工作线程,用户线程或服务线程,低优先级别,通过将工作线程设置成后台线程,调用线程终止后,则工作线程也自动退出,否则主程序会一直等待工作线程结束后才会退出,如下demo示例:
static void Main(string[] args)
{
Thread work_normal = new Thread(WorkMethod_Norml);
Thread work_loop = new Thread(WorkMethod_loop);
work_normal.IsBackground = false;//该线程和Main()主线程结束,则后台线程被终止
work_loop.IsBackground = true;
work_loop.Start();
work_normal.Start();
Console.WriteLine("main thread finished");
}
static void WorkMethod_Norml()
{
for (int i = 0; i < 10; i++)
{
Thread.Sleep(100);
Console.WriteLine("Normal" + i);
}
Console.WriteLine("前台线程结束");
}
static void WorkMethod_loop()
{
int i = 0;
while (i<1000)
{
Console.WriteLine("Back" + ++i );
Thread.Sleep(100);
}
Console.WriteLine("后台线程结束");
}
3.线程中断Inrerrupt():
当线程处于非运行状态,即当线程调用了Join或Sleep方法,线程处于阻塞状态,此时调用Interrupt(),工作线程会抛出一个中断异常,处于阻塞状态的线程会捕捉到这个异常,以提早结束阻赛状态,如下demo:
static void Main(string[] args)
{
//Using System.Threading
//定义工作线程
Thread workThread_interrupt = new Thread(DoWork_Interrupt);
//启动工作线程
workThread_interrupt.Start();
Console.WriteLine("请在10秒内中按任意键中断线程");
Console.ReadKey();
//当工作线程调用了Join或Sleep方法,处于非运行状态,即线程处于阻塞状态
//此时调用Interrupt(),工作线程抛中断异常,处于阻塞状态的线程会捕捉到这个异常,结束阻赛状态.
workThread_interrupt.Interrupt();
//阻塞当前线程,即main()主线程,把主线程加入到工作线程的尾部,当工作线程执行完毕后再执行main()主线程
workThread_interrupt.Join();
Console.WriteLine("main thread finshed");
Console.ReadKey();
}
static void DoWork_Interrupt()
{
try
{
Console.WriteLine("工作线程预计休眠10秒");
Thread.Sleep(10000);
Console.WriteLine("工作作线程结束");
}
catch(ThreadInterruptedException ex_interrupt)
{
Console.WriteLine("睡眠中断");
}
}
4.线程优先级:这个比较好理解,优先级越高,获得的资源倾斜越多:
CPU同时执行不同的线程,会尽可能多的分配时间给高优先级线程,可以按照毫秒级别进行分配,高优先级的线程操作自然就执行快一些.
ThreadPriority属性:Highest,AboveNormal,Nomral,BelowNormal,Lowest,5各级别,默认是Normal级别.(Bold 24-04-24)
static void Main(string[] args)
{
Thread work_high = new Thread(WorkMethod_High);//定义工作线程high
Thread work_low = new Thread(WorkMethod_low);//定义工作线程low
work_high.Priority = ThreadPriority.Highest;//设置线程优先级
work_low.Priority = ThreadPriority.Lowest;//设置线程优先级
work_low.Start();//启动线程
work_high.Start();//启动线程
Thread.Sleep(10000);//主线程休眠,工作线程正常执行
work_low.Abort();//模拟异常中断线程,工作线程捕获该异常
work_high.Abort();//模拟异常中断线程,工作线程捕获该异常
work_high.Join();//
work_low.Join();//
Console.WriteLine("main thread finished");
Console.ReadKey();
}
static void WorkMethod_High()
{
long cnt = 0;
try
{
while(true)
{
cnt++;
}
}
catch(ThreadAbortException ex)
{
Console.WriteLine("High:" + cnt.ToString());
}
}
static void WorkMethod_low()
{
long cnt_low = 0;
try
{
while (true)
{
cnt_low++;
}
}
catch (ThreadAbortException ex)
{
Console.WriteLine("Low:" + cnt_low.ToString());
}
}
}
5.线程状态控制:(Bold 24-04-24)
创建线程后,UnStarted状态
Start后,调度资源开始执行后,Running状态
Thread.Sleep(XX)方法后,WaitSleepJoin状态,线程睡眠状态,不执行.
线程对象Abort()方法后,会抛出一个ThreadAbortException异常,线程停止.(VS2022已停止支持该方法,大概是操作太暴力了吧~~)
线程对象执行Join()方法后,调用线程会等待线程对象的操作完成后,才会继续往下执行,即Join()方法将线程操作加入到调用线程中去,变成同步操作了.
比如调用放线程M,创建了服务线程S后,M和S代码并行执行,如果在M线程中执行S.Joing()方法后,S线程还没有结束,则S线程的未结束操作加入到M线程中来,即M线程等待S线程操作完再继续往下执行M线程的代码.
简单说就是线程对象的Join方法,把M和S线程的异步关系,编程了同步关系,这个要好好理解.
6.线程池,
Net提供的线程池ThreadPool类
线程池(threadPool)创建的线程是后台线程,且不能修改线程池创建的线程为前台线程,也不能设置其优先级.
如果我们想完整的演示线程池创建的线程任务,注意此时主线程不要结束,否则创建的工作线程也就因为主线程结束了而被自动终止了.
Thread创建线程需要时间,比如几十毫秒,如果创建的线程比较多,那么会比较浪费时间,这就用到了线程池.
在线程池中预先创建N多线程是闲置状态,需要线程异步执行时,就拿出一个闲置线程来执行,把创建线程变成了分配线程,减少资源开销。
打个比方,比如我有一个项目开工,需要ABCDE...H很多工种才能完成,项目进行到什么程度需要什么样的工种,我再去找,这就类似于线程的创建。
而如果我先把需要的工种人员都招齐了,项目开工后,需要什么工种,就上什么工种,这就类似于线程池分配.(Bold 24-04-24)
.Net预设的线程池可以定义线程的最小最大线程数量,可用线程数量等.
一旦线程池中某个任务完成了,该线程就回到线程池等待线程队列中,有任务需要线程时,就从线程池分配闲置的线程执行任务,若没有闲置线程可用,就等待线程释放出来再执行.
线程池,ThreadPool类是静态类,其所有成员都是静态的.通过静态方法QueueUserWorkItem,可以将工作线程任务加到线程池队列中,有空闲线程可用时任务开始执行.
常用的委托异步调用BeginInvoke(),定时器timer,任务Task都是CLR(Common Language Runtime)的线程池.
public static bool QueueUserWorkItem(WaitCallback callBack, object state);
public static bool QueueUserWorkItem(WaitCallback callBack);
QueueUserWorkItem参数为回调函数WaitCallBack,
该委托类型定义如下:public delegate void WaitCallback(object state);
我们注意看,QueueUserWorkItem方法有两个重载,一个是只有回调函数WaitCallback,这个委托类型引用的方法,需要传入一个object参数,但是在没有传入object参数,只有方法的情况下,也能执行,原因是.Net帮我们传入了一个null, 也就是说不是没有传入参数,而是默认传入了null作为object参数值.
static void CallbackWithoutParameter(object o)
{
Console.WriteLine("123123");
Thread.Sleep(1000);
Console.WriteLine("123123");
Thread.Sleep(1000);
Console.WriteLine("123123");
Thread.Sleep(1000);
Console.WriteLine("123123");
Thread.Sleep(1000);
}
static void Main(string[] args)
{
//.Net帮我们默认传入null,作为object参数值
ThreadPool.QueueUserWorkItem(CallbackWithoutParameter);
//手动传入null作为参数
ThreadPool.QueueUserWorkItem(_ => CallbackWithoutParameter(null));
//.Net帮我们默认传入null,作为object参数值
ThreadPool.QueueUserWorkItem(_ => Console.WriteLine("线程池分配的线程正在执行...."));
Thread.Sleep(9000);
}
下面的demo是给方法QueueUserWorkItem传入一个object参数,在QueueUserWorkItem方法内部,这个对象值给回调函数使用:
public static bool QueueUserWorkItem(WaitCallback callBack, object state);
static void Main(string[] args)
{
//public static bool QueueUserWorkItem(WaitCallback callBack, object state);
//public static bool QueueUserWorkItem(WaitCallback callBack);
//QueueUserWorkItem参数为回调函数WaitCallBack,该委托类型定义如下:public delegate void WaitCallback(object state);
object obj = "ThreadPool demo obj";
ThreadPool.QueueUserWorkItem(DoWork,obj);
Console.ReadKey();
//System.Threading.Timer
//一种定时器工具,用来在后台线程定期计划执行任务
//Timer类型构造函数的参数是回调函数TimerCallback: public delegate void TimerCallback(object state);
Timer Mytimer = new Timer(DoWork, "MyTimer", 0, 200);//每200马上执行一次回调函数DoWork
Mytimer.Change(0, 1000);//更新timer的interval参数200->1000ms.
Console.ReadKey();
}
static void DoWork(object obj)
{
Console.WriteLine($"{obj}开始执行工作线程任务");
Thread.Sleep(200);//模拟工作线程任务
Console.WriteLine("...");
Thread.Sleep(200);//模拟工作线程任务
Console.WriteLine("......");
Thread.Sleep(200);//模拟工作线程任务
Console.WriteLine(".........");
Console.WriteLine("..........");
Thread.Sleep(200);//模拟工作线程任务
Console.WriteLine("............");
Thread.Sleep(200);//模拟工作线程任务
Console.WriteLine("...............");
Console.WriteLine($"{obj}工作线程任务结束");
}
执行结果如下