C#多线程——Thread睡眠、中断、合并、暂停与恢复、终止

一、睡眠

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(),现在实现线程的暂停与灰度可以通过AutoResetEventManualResetEvent这两个阻塞事件类来实现。

两种事件的不同

  • 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()
  1. 取消终止线程的请求,并恢复线程,继续执行ResetAbort后面的语句,然后执行catch块之后的语句
  2. 若Catch块之后有Finally块,则执行Finally块,然后执行Finally块后面的语句。
  • 如果没有调用Thread.ResetAbort()
  1. 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调用的时间
  1. 线程Start之前调用Abort
    线程会在Start被调用时终止线程。

  2. 线程Sleeping的时候调用Abort
    线程被中断,然后终止线程

  3. 线程Blocked的时候调用Abort
    线程被中断,然后终止线程

  4. 线程被挂起的时候调用Abort
    Throw ThreadStartException 引发Abort的调用,然后AbortRequested 被加到正在被终止的线程的ThreadState属性

  5. 一个托管线程正在执行非托管代码时调用Abort
    ThreadAbortException不会被抛出直到线程返回托管代码。

  6. 如果同时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
	}

你可能感兴趣的:(C#,c#)