一、睡眠
Sleep(int millisecondsTimeout)
Thread.Sleep(int millisecondsTimeout)还有一个重载Thread.Sleep(TimeSpan timeout),作用是将当前线程挂起一定的时长,期间放弃CPU使用权,时间到了自动恢复线程和其他线程一起参与CPU的竞争。
//调用——Thread类的静态方法
Thread.Sleep(1000);//将当前所在进程挂起1000ms
Timespan ts=new Timespan(1,20,33);//创建一个时间间隔对象,长度为1h22min33s
Thread.Sleep(ts);//将当前线程挂起1h22min33s
SpinWait(int iterations)
Thread.SpinWait(int iterations)也是Thread类的静态方法,将当前线程等待一定长的时间(ms为单位),期间占用时间片,不放弃CPU的使用权,类似于让CPU执行一段无效代码。
//调用
Thread.SpinWait(1000);//将当前线程等待1000ms,等待结束后可以立即执行不需要重新参与CPU竞争
二、中断Interrupt
Interrupt方法只可以中断处于 WaitSleepJoin 状态的线程,抛出异常并改变该线程的状态,线程将恢复执行。
如果一个线程处于阻塞状态(如调用了Sleep()、Join()等阻塞方法以及可中断通道的I/O操作后的阻塞),则在线程状态为WaitSleepJoin状态时,就会在阻塞方法调用处抛出ThreadInterruptException异常,并且在抛出异常后将线程状态设置为其他状态,从而线程从阻塞状态醒过来,并在结束线程前让程序员有足够的时间来处理中断请求。
//调用
thread.Interupt();
三、合并Join
thread.Join()表示thread线程终止之前阻止调用其他线程,thread终止之后自动恢复原来的线程,通常在其他线程或者主线程中使用此方法,起到临时强制优先执行特定线程的效果。
//调用
thread.Join();//可以有参数,表示合并的时长
四、暂停与恢复
C#已经弃用了不安全的Suspend()和Resume(),现在实现线程的暂停与灰度可以通过AutoResetEvent和ManualResetEvent这两个阻塞事件类来实现。
两种事件的不同
- AutoResetEvent是自动阻塞,同时只允许一个线程通过一次,然后继续阻塞,类似于小区门口测体温
- ManualResetEvent是手动阻塞,打开后需要手动阻塞,否则会一直打开;阻塞后需要手动打开,否则会一直阻塞,类似于水龙头
Set()、Reset()与WaitOne()
- WaitOne()阻塞线程,等待信号;有带参数的重载,设置等待时间以及等待超时的操作
- Set()方法可以发出可通过信号
- Reset()方法可以发出不可通过信号
使用
- AutoResetEvent
public class
{
//实例化AutoResetEvent对象,参数bool表示are初始的状态,true表示通过
private static AutoResetEvent are = new AutoResetEvent(true);
Thread th=new Thread(new ThreadStart(ThTest));
private static ThTest()
{
are.WaitOne();
Console.WriteLine(1);
are.WaitOne();
Console.WriteLine(2);
are.WaitOne();
Console.WriteLine(3);
}
主函数()
{
th.Start();
Console.ReadLine();
are.Set();
}
//输出
1
2
//解析
//遇到第一个are.WaitOne()时,由于阻塞事件对象are的初始状态为可通过(true),所以th直接通过,同时are的状态改为不可通过,接着输出1
//遇到第二个are.WaitOne()时,由于are的状态为不可通过,所以th被are阻塞,等待其他线程发送信号。
//主线程上are.Set()发送了are处可通过的信号,th通过第二个are.WaitOne(),同时are的状态改为不可通过,输出2
//遇到第三个are.WaitOne(),th又被阻塞,它只能等待,直到主线程结束也没有通过,th被强行终止
//AutoResetResult中的Reset()常用于线程内部自阻塞
}
- ManualResetEvent
public class
{
//实例化ManualResetEvent对象,参数bool表示mre初始的状态,false表示不通过
private static ManualResetEvent mre = new ManualResetEvent(false);
Thread th=new Thread(new ThreadStart(ThTest));
private static ThTest()
{
mre.WaitOne();
Console.WriteLine(1);
mre.WaitOne();
Console.WriteLine(2);
mre.Reset();
mre.WaitOne();
Console.WriteLine(3);
mre.WaitOne();
Console.WriteLine(4);
}
主函数()
{
th.Start();
Console.ReadLine();
mre.Set();
}
//输出
1
2
//解析
//遇到第一个mre.WaitOne()时,由于阻塞事件对象mre的初始状态为不可通过(false),所以th阻塞
//这时主线程发送mre.Set()使mre变为通过状态,th通过第一个mre.WaitOne(),输出1
//紧接着遇到第二个mre.WaitOne(),mre状态还是通过,th通过第二个mre.WaitOne(),输出2
//这时线程内部将mre状态置为不可通过
//遇到第三个mre.WaitOne()时,th被阻塞,mre状态为不可通过,th一直等待
//直到主线程结束也没有通过,th被强行终止
}
线程中止
线程一旦中止就无法再重启。
- 协作式取消(Cooperative Cancellation)
- Abort()
- return
协作式取消(Cooperative Cancellation)——正确停止线程
- 类似于AutoResetEvent和ManualResetEvent,通过在线程自身内部开放给调用者接口(传递信号量),并在工作时检测对应的标识:++Cancled++,实现响应调用者的请求。
- 与Abort()不同的是:不在于用户采取了什么行为,而在于线程是否能主动响应停止请求。
- 机制:线程在工作时以某种频率检测Cancled标识,若检测到Cancled,则线程自己负责退出。
static void Main(string[] args)
{
CancellationTokenSource cts = new CancellationTokenSource();
Thread t = new Thread(() =>
{
while(true)
{
if(cts.Token.IsCancellationRequested)
{
Console.WriteLine("Cancled信号触发!");
Console.WriteLine(Thread.CurrentThread.ThreadState.ToString());
break;
}
Console.WriteLine(DateTime.Now.ToString());
Thread.Sleep(1000);
}
Console.WriteLine("线程终止!");
});
t.Start();
Console.ReadLine();
cts.Cancel();
Console.WriteLine("main:线程已停止!");
Console.WriteLine(t.ThreadState.ToString());
Console.ReadKey();
Thread.Sleep(2000);
Console.WriteLine(t.ThreadState.ToString());
Console.ReadKey();
}
//输出
2021/6/2 11:38:47
2021/6/2 11:38:48
2021/6/2 11:38:49
2021/6/2 11:38:50
2021/6/2 11:38:51
main:线程已停止!
WaitSleepJoin
Cancled信号触发!
Running
线程终止!
Stopped
Abort() 立即终止一个线程
Abort()终止当前线程时在当前线程上引发ThreadAbortException 异常,且只对托管代码有用。ThreadAbortExcetion是一个可以被应用程序捕获的特殊异常,在catch 块中会自动重新抛出这个异常,除非在catch块中调用ResetAbort方法。Thread.ResetAbort()可以取消掉终止的请求,而且可以防止catch中再次抛出的ThreadAbortException终止当前线程。未执行的Finally块会在线程终止前执行。
tips:1·托管代码指的是由CLR负责管理执行(托管)的代码,往往以IL(中间语言)的形式被CLR执行;2·非托管代码指的是由操作系统直接负责在机器上执行的代码,不享受CLR提供的内存管理等服务,通常为MFC、WIN32、ALT等项目。
详解
public class ThreadWork
{
public static void DoWork()
{
try
{
... //try的内容
}
catch (ThreadAbortException e)
{
... //catch的内容
//Thread.ResetAbort();
... //catch的内容
}
finally
{
... //finally内容
}
... //线程剩余内容
}
}
- 如果在catch中调用Thread.ResetAbort()
- 取消终止线程的请求,并恢复线程,继续执行ResetAbort后面的语句,然后执行catch块之后的语句
- 若Catch块之后有Finally块,则执行Finally块,然后执行Finally块后面的语句。
- 如果没有调用Thread.ResetAbort()
- finally块在线程终止前执行,finally块之后的语句不会被执行,然后线程终止。
- catch和finally中的代码一定会被执行
用法
// 线程内部写法参考【详解】
// 其他线程中的部分:由于Abort()后还要执行一部分代码,所以线程不会即刻停止。为防止catch和finally中的代码耗时过长而影响其他线程,我们需要做一定的操作来等待线程的完成或终止。
//1. 循环等待
th.Abort();
while(th.ThreadState!=ThreadState.Aborted)
{
//当调用Abort方法后,如果thread线程的状态不为Aborted,主线程就一直在这里做循环,直到thread线程的状态变为Aborted为止
Thread.Sleep(100);
}
//当跳出上面的循环后就表示我们启动的线程thread已经完全终止了
···
//2. Join等待
th.Abort();
th.Join();
···
Abort调用的时间
线程Start之前调用Abort
线程会在Start被调用时终止线程。线程Sleeping的时候调用Abort
线程被中断,然后终止线程线程Blocked的时候调用Abort
线程被中断,然后终止线程线程被挂起的时候调用Abort
Throw ThreadStartException 引发Abort的调用,然后AbortRequested 被加到正在被终止的线程的ThreadState属性一个托管线程正在执行非托管代码时调用Abort
ThreadAbortException不会被抛出直到线程返回托管代码。如果同时Abort两个线程,有可能一个线程会设置状态信息,而另外一个线程执行Abort的方法。然而,应用程序不会检测到这种情形。
注意Abort方法是通过抛出ThreadAbortException异常而强制结束线程,尽量少用。
万能return
多线程其实是调用一个方法栈,return可以结束方法,要说不好的地方就是
可能造成return污染,使其他方法受到影响。
协作式停止线程是官方推荐写法,个人理解原理就是传递一个全局变量来通知线程,没必要使用它给的那个类,可能有其他的用处。个人更喜欢Abort()和return,停止请求发出后需要进行一份操作就用Abort(),因为可以捕捉异常;只需要立刻停掉就return。
附:ThreadState状态表
public enum ThreadState
{
Running = 0x0,
StopRequested = 0x1,
SuspendRequested = 0x2,
Background = 0x4,//后台线程会随着主线程结束而(强制)结束,前台线程全部执行完主线程才能结束
Unstarted = 0x8,
Stopped = 0x10,
WaitSleepJoin = 0x20,
Suspended = 0x40,
AbortRequested = 0x80,
Aborted = 0x100
}