前言

众所周知,在Thread类中的挂起线程和恢复线程微软已标记过时的,因为可能会造成问题,具体的可以自己去看官方介绍,或查看我的前面几篇博客:

Thread

Resume()   恢复当前线程



已过时。 Resumes a thread that has been suspended.


Thread

Suspend()   挂起当前线程



已过时。 挂起线程,或者如果线程已挂起,则不起作用。


其他方式实现

一、自定义ThreadWorkItem类



C# 自定义Thread挂起线程和恢复线程_信号量

class ThreadWorkItem
{
public int ThreadManagerId { get; set; }
public Thread Thread { get; set; }
public string ThreadName { get; set; }
public bool StopFlag { get; set; }
public ManualResetEvent ManualResetEvent { get; set; }

}


C# 自定义Thread挂起线程和恢复线程_重置_02


 

二、C# Thread挂起线程和恢复线程的实现的两种方式

 

方式1:使用变量开关控制挂起线程和恢复线程,具体代码如下


C# 自定义Thread挂起线程和恢复线程_i++_03C# 自定义Thread挂起线程和恢复线程_重置_04


C# 自定义Thread挂起线程和恢复线程_信号量_05

public class Program
{
//线程工作集合
private static List<ThreadWorkItem> Works = new List<ThreadWorkItem>();

//方式1:使用变量开关控制挂起线程和恢复线程

private static void Main(string[] args)
{
ThreadWorkItem wItem = null;
Thread t = null;

var threadNum = 2;


for (int i = 0; i < threadNum; i++)
{

t = new Thread(o=>
{
var w = o as ThreadWorkItem;
if (w == null) return;
while (true)
{
if (!w.StopFlag)
{
Console.WriteLine("我是线程:" + Thread.CurrentThread.Name);
Thread.Sleep(1000);
continue;
}
//避免CPU空转
Thread.Sleep(5000);

}

});
//$ C#6.0语法糖
t.Name = $"Hello I'am 线程:{i}-{t.ManagedThreadId}";
wItem = new ThreadWorkItem
{
StopFlag = false,
Thread = t,
ThreadManagerId = t.ManagedThreadId,
ThreadName = t.Name
};
Works.Add(wItem);
t.Start(Works[i]);
}

//5秒后允许一个等待的线程继续。当前允许的是线程1
Thread.Sleep(5000);
Works[0].StopFlag = true;
Console.WriteLine($"thread-{Works[0].ThreadName} is 暂停");



//5秒后允许一个等待的线程继续。当前允许的是线程0,1
Thread.Sleep(5000);
Works[0].StopFlag = false;
Console.WriteLine($"thread-{Works[0].ThreadName} is 恢复");


}
}


C# 自定义Thread挂起线程和恢复线程_i++_06

View Code

 

方式2:使用ManualResetEvent控制挂起线程和恢复线程(推荐);替代Thread类中被微软标记过时的函数(内核模式非混合模式)


C# 自定义Thread挂起线程和恢复线程_i++_03C# 自定义Thread挂起线程和恢复线程_重置_04


C# 自定义Thread挂起线程和恢复线程_i++_09

public class Program
{
//线程工作集合
static List<ThreadWorkItem> Works = new List<ThreadWorkItem>();

//方式2:使用ManualResetEvent控制挂起线程和恢复线程(推荐);替代Thread类中被微软标记过时的函数
static void Main(string[] args)
{

Task.Factory.StartNew(() =>
{
Thread t = null;
ThreadWorkItem item = null;
for (int i = 0; i < 2; i++)
{
t = new Thread((o) =>
{
var w = o as ThreadWorkItem;
if (w == null) return;

while (true)
{
//阻塞当前线程
w.ManualResetEvent.WaitOne();

Console.WriteLine("我是线程:" + Thread.CurrentThread.Name);
Thread.Sleep(1000);
}

});
t.Name = "Hello,i 'am Thread: " + i;
item = new ThreadWorkItem
{
//线程0,1持续运行,设置true后非阻塞,持续运行。需要手动触发Reset()才会阻塞实例所在当前线程
ManualResetEvent = new ManualResetEvent(true),
Thread = t,
ThreadManagerId = t.ManagedThreadId,
ThreadName = t.Name
};
Works.Add(item);
t.Start(item);
}



//5秒后准备暂停一个线程1。线程0持续运行

Thread.Sleep(5000);
Console.WriteLine("close...");
Works[1].ManualResetEvent.Reset();

//5秒后恢复线程1;线程0,1持续运行
Thread.Sleep(5000);
Console.WriteLine("open...");
Works[1].ManualResetEvent.Set();

//5秒后准备暂停一个线程0。线程1持续运行
Thread.Sleep(5000);
Console.WriteLine("close0...");
Works[0].ManualResetEvent.Reset();

//5秒后恢复线程1;线程0,1持续运行
Thread.Sleep(5000);
Console.WriteLine("open0...");
Works[0].ManualResetEvent.Set();

});


Console.ReadLine();
}

}


C# 自定义Thread挂起线程和恢复线程_信号量_10

View Code

 

三、总结

 1.有时候会觉得必须由主线程创建ManualResetEvent实例才能起到作用,实际并不是这样的,上述方式2则证明了这一点。

 

 2.那么AutoResetEvent做不到同样的效果吗?

     答:AutoResetEvent 顾名思义,自动Reset()表示重置信号量状态,则当前线程中持有WaitOne()就又会被持续阻塞。而ManualResetEvent必须要手动调用Reset()才能重置信号量阻塞当前线程。

     这里再解释下Set(),它表明允许一个或多个被同一个ManualResetEvent实例WaitOne()的线程放行。

 

 3.实例化信号量的构造参数是什么意思?如:new ManualResetEvent(true)

     答:信号量的终止状态,如果值为false则表明终止状态,调用WaitOne()方法的时候立即阻塞。设置true可以理解为隐式调用了Set()方法放行一次。

 

 4. ManualResetEvent和AutoResetEvent的区别

     答:ManualResetEvent调用Set()允许一个或多个被同一个ManualResetEvent实例WaitOne()的线程放行。

           AutoResetEvent调用Set() 只能允许一个线程放行。如果多处使用同一个实例,则需要手动调用多次Set(),并且会自动调用Reset方法


 

 

=======================================================================================

C# ManualResetEvent 的介绍

 

名称

说明

1.

Close

在派生类中被重写时,释放由当前 WaitHandle 持有的所有资源。 (继承自 WaitHandle。)

在XNA Framework中,此成员由 Close() 重写。

2.

CreateObjRef

创建一个对象,该对象包含生成用于与远程对象进行通信的代理所需的全部相关信息。 (继承自 MarshalByRefObject。)

3.

Dispose()

释放由 WaitHandle 类的当前实例使用的所有资源。 (继承自 WaitHandle。)

4.

Equals(Object)

确定指定的对象是否等于当前对象。 (继承自 Object。)

5.

GetAccessControl

获取 EventWaitHandleSecurity 对象,该对象表示由当前 EventWaitHandle 对象表示的已命名系统事件的访问控制安全性。 (继承自 EventWaitHandle。)

6.

GetHashCode

作为默认哈希函数。 (继承自 Object。)

8.

GetLifetimeService

检索控制此实例的生存期策略的当前生存期服务对象。 (继承自 MarshalByRefObject。)

9.

GetType

获取当前实例的 Type。 (继承自 Object。)

10.

InitializeLifetimeService

获取控制此实例的生存期策略的生存期服务对象。 (继承自 MarshalByRefObject。)

11.

Reset

将事件状态设置为非终止状态,导致线程阻止。 (继承自 EventWaitHandle。)

12.

Set

将事件状态设置为终止状态,允许一个或多个等待线程继续。 (继承自 EventWaitHandle。)

13.

SetAccessControl

设置已命名的系统事件的访问控制安全性。 (继承自 EventWaitHandle。)

14.

ToString

返回表示当前对象的字符串。 (继承自 Object。)

15.

WaitOne()

阻止当前线程,直到当前 WaitHandle 收到信号。 (继承自 WaitHandle。)

在XNA Framework中,此成员由 WaitOne() 重写。

16.

WaitOne(Int32)

阻止当前线程,直到当前 WaitHandle 收到信号,同时使用 32 位带符号整数指定时间间隔。 (继承自 WaitHandle。)

17.

WaitOne(TimeSpan)

阻止当前线程,直到当前实例收到信号,同时使用 TimeSpan 指定时间间隔。 (继承自 WaitHandle。)

18.

WaitOne(Int32, Boolean)

阻止当前线程,直到当前的 WaitHandle 收到信号为止,同时使用 32 位带符号整数指定时间间隔,并指定是否在等待之前退出同步域。 (继承自 WaitHandle。)

在XNA Framework中,此成员由 WaitOne(Int32, Boolean) 重写。

19.

WaitOne(TimeSpan, Boolean)

使用 TimeSpan 指定时间间隔并指定是否在等待之前退出同步域,以此阻止当前线程,直到当前的实例收到信号。 (继承自 WaitHandle。)

 

前言

众所周知,在Thread类中的挂起线程和恢复线程微软已标记过时的,因为可能会造成问题,具体的可以自己去看官方介绍,或查看我的前面几篇博客:

Thread

Resume()   恢复当前线程



已过时。 Resumes a thread that has been suspended.


Thread

Suspend()   挂起当前线程



已过时。 挂起线程,或者如果线程已挂起,则不起作用。


其他方式实现

一、自定义ThreadWorkItem类



C# 自定义Thread挂起线程和恢复线程_i++_11

class ThreadWorkItem
{
public int ThreadManagerId { get; set; }
public Thread Thread { get; set; }
public string ThreadName { get; set; }
public bool StopFlag { get; set; }
public ManualResetEvent ManualResetEvent { get; set; }

}


C# 自定义Thread挂起线程和恢复线程_重置_12


 

二、C# Thread挂起线程和恢复线程的实现的两种方式

 

方式1:使用变量开关控制挂起线程和恢复线程,具体代码如下


C# 自定义Thread挂起线程和恢复线程_i++_03C# 自定义Thread挂起线程和恢复线程_重置_04


C# 自定义Thread挂起线程和恢复线程_时间间隔_15

public class Program
{
//线程工作集合
private static List<ThreadWorkItem> Works = new List<ThreadWorkItem>();

//方式1:使用变量开关控制挂起线程和恢复线程

private static void Main(string[] args)
{
ThreadWorkItem wItem = null;
Thread t = null;

var threadNum = 2;


for (int i = 0; i < threadNum; i++)
{

t = new Thread(o=>
{
var w = o as ThreadWorkItem;
if (w == null) return;
while (true)
{
if (!w.StopFlag)
{
Console.WriteLine("我是线程:" + Thread.CurrentThread.Name);
Thread.Sleep(1000);
continue;
}
//避免CPU空转
Thread.Sleep(5000);

}

});
//$ C#6.0语法糖
t.Name = $"Hello I'am 线程:{i}-{t.ManagedThreadId}";
wItem = new ThreadWorkItem
{
StopFlag = false,
Thread = t,
ThreadManagerId = t.ManagedThreadId,
ThreadName = t.Name
};
Works.Add(wItem);
t.Start(Works[i]);
}

//5秒后允许一个等待的线程继续。当前允许的是线程1
Thread.Sleep(5000);
Works[0].StopFlag = true;
Console.WriteLine($"thread-{Works[0].ThreadName} is 暂停");



//5秒后允许一个等待的线程继续。当前允许的是线程0,1
Thread.Sleep(5000);
Works[0].StopFlag = false;
Console.WriteLine($"thread-{Works[0].ThreadName} is 恢复");


}
}


C# 自定义Thread挂起线程和恢复线程_信号量_16

View Code

 

方式2:使用ManualResetEvent控制挂起线程和恢复线程(推荐);替代Thread类中被微软标记过时的函数(内核模式非混合模式)


C# 自定义Thread挂起线程和恢复线程_i++_03C# 自定义Thread挂起线程和恢复线程_重置_04


C# 自定义Thread挂起线程和恢复线程_c#_19

public class Program
{
//线程工作集合
static List<ThreadWorkItem> Works = new List<ThreadWorkItem>();

//方式2:使用ManualResetEvent控制挂起线程和恢复线程(推荐);替代Thread类中被微软标记过时的函数
static void Main(string[] args)
{

Task.Factory.StartNew(() =>
{
Thread t = null;
ThreadWorkItem item = null;
for (int i = 0; i < 2; i++)
{
t = new Thread((o) =>
{
var w = o as ThreadWorkItem;
if (w == null) return;

while (true)
{
//阻塞当前线程
w.ManualResetEvent.WaitOne();

Console.WriteLine("我是线程:" + Thread.CurrentThread.Name);
Thread.Sleep(1000);
}

});
t.Name = "Hello,i 'am Thread: " + i;
item = new ThreadWorkItem
{
//线程0,1持续运行,设置true后非阻塞,持续运行。需要手动触发Reset()才会阻塞实例所在当前线程
ManualResetEvent = new ManualResetEvent(true),
Thread = t,
ThreadManagerId = t.ManagedThreadId,
ThreadName = t.Name
};
Works.Add(item);
t.Start(item);
}



//5秒后准备暂停一个线程1。线程0持续运行

Thread.Sleep(5000);
Console.WriteLine("close...");
Works[1].ManualResetEvent.Reset();

//5秒后恢复线程1;线程0,1持续运行
Thread.Sleep(5000);
Console.WriteLine("open...");
Works[1].ManualResetEvent.Set();

//5秒后准备暂停一个线程0。线程1持续运行
Thread.Sleep(5000);
Console.WriteLine("close0...");
Works[0].ManualResetEvent.Reset();

//5秒后恢复线程1;线程0,1持续运行
Thread.Sleep(5000);
Console.WriteLine("open0...");
Works[0].ManualResetEvent.Set();

});


Console.ReadLine();
}

}


C# 自定义Thread挂起线程和恢复线程_c#_20

View Code

 

三、总结

 1.有时候会觉得必须由主线程创建ManualResetEvent实例才能起到作用,实际并不是这样的,上述方式2则证明了这一点。

 

 2.那么AutoResetEvent做不到同样的效果吗?

     答:AutoResetEvent 顾名思义,自动Reset()表示重置信号量状态,则当前线程中持有WaitOne()就又会被持续阻塞。而ManualResetEvent必须要手动调用Reset()才能重置信号量阻塞当前线程。

     这里再解释下Set(),它表明允许一个或多个被同一个ManualResetEvent实例WaitOne()的线程放行。

 

 3.实例化信号量的构造参数是什么意思?如:new ManualResetEvent(true)

     答:信号量的终止状态,如果值为false则表明终止状态,调用WaitOne()方法的时候立即阻塞。设置true可以理解为隐式调用了Set()方法放行一次。

 

 4. ManualResetEvent和AutoResetEvent的区别

     答:ManualResetEvent调用Set()允许一个或多个被同一个ManualResetEvent实例WaitOne()的线程放行。

           AutoResetEvent调用Set() 只能允许一个线程放行。如果多处使用同一个实例,则需要手动调用多次Set(),并且会自动调用Reset方法