C# 线程间互相通信 AutoResetEvent和ManualResetEvent

C#线程间互相通信主要用到两个类:AutoResetEvent和ManualResetEvent。

一、AutoResetEvent

AutoResetEvent 允许线程通过发信号互相通信,线程通过调用 AutoResetEvent 上的 WaitOne 来等待信号。 如果 AutoResetEvent 为非终止状态,则线程会被阻止,并等待当前控制资源的线程通过调用 Set 来通知资源可用。

下面我用吃快餐的例子来说明这个问题,吃快餐的时候都会排队付款,收银员发送收款通知,客户依次付钱,代码如下: 

 1  class Program
 2  {
 3      //若要将初始状态设置为终止,则为 true;若要将初始状态设置为非终止,则为 false
 4      static AutoResetEvent autoResetEvent = new AutoResetEvent(false);
 5  
 6      static void Main(string[] args)
 7      {
 8          Thread t1 = new Thread(() =>
 9          {
10              Console.WriteLine("客户1在排队等待付钱...");
11  
12              //客户1调用AutoResetEvent上的WaitOne来等待付钱通知
13              autoResetEvent.WaitOne();
14              Console.WriteLine("通知来了,客户1付钱"); 
15          });
16          t1.IsBackground = true;
17          t1.Start();
18  
19          Pay();//发送通知 
20          Console.ReadKey();
21      }
22      
23      static void Pay()
24      {
25          string tip = Console.ReadLine();
26          if (tip == "next")
27          {
28              autoResetEvent.Set();//收银员发送付钱通知,通过调用Set来通知客户付钱
29          }
30      }
31  }

  运行在屏幕中打印:

客户1在排队等待付钱...

等收银员说“next”的时候,向客户1发送付钱通知(autoResetEvent.Set()),屏幕打印:

客户1在排队等待付钱...
next
通知来了,客户1付钱!

AutoResetEvent类一次只能通知一个等待的线程,且通知一次过后会立即将AutoResetEvent对象的状态置为false,也就是如果有两个客户都在等待收银员通知,AutoResetEvent对象的set方法只能通知到第一个客户,代码和效果如下: 

 1  class Program
 2  {
 3      //若要将初始状态设置为终止,则为 true;若要将初始状态设置为非终止,则为 false。
 4      static AutoResetEvent autoResetEvent = new AutoResetEvent(false);
 5  
 6      static void Main(string[] args)
 7      {
 8          Thread t1 = new Thread(() =>
 9          {
10              Console.WriteLine("客户1在排队等待付钱...");
11  
12              //客户1调用AutoResetEvent上的WaitOne来等待付钱通知
13              autoResetEvent.WaitOne();
14              Console.WriteLine("通知来了,客户1付钱");
15          });
16          t1.IsBackground = true;
17          t1.Start();
18  
19          Thread t2 = new Thread(() =>
20          {
21              Console.WriteLine("客户2在排队等待付钱...");
22  
23              //客户2调用AutoResetEvent上的WaitOne来等待付钱通知
24              autoResetEvent.WaitOne();
25              Console.WriteLine("通知来了,客户2付钱!");
26          });
27          t2.IsBackground = true;
28          t2.Start();
29  
30          Pay();//发送通知
31  
32          Console.ReadKey();
33      }
34      
35      static void Pay()
36      {
37          string tip = Console.ReadLine();
38          if (tip == "next")
39          {
40              autoResetEvent.Set();//收银员发送付钱通知,通过调用Set来通知客户付钱
41          }
42      }
43  }

运行后屏幕打印:

客户1在排队等待付钱...
客户1在排队等待付钱...

next
通知来了,客户1付钱! 

这就说明在通知完客户1后,autoResetEvent 的状态又被置为了false,这时如果要通知到客户2,就需要在通知完客户1后,再执行一次通知,在线程1中加上一行代码,如下: 

 1  Thread t1 = new Thread(() =>
 2  {
 3      Console.WriteLine("客户1在排队等待付钱...");
 4  
 5      //客户1调用AutoResetEvent上的WaitOne来等待付钱通知
 6      autoResetEvent.WaitOne();
 7      Console.WriteLine("通知来了,客户1付钱");
 8  
 9      autoResetEvent.Set();//让其再通知下个客户
10  });

运行后屏幕打印:

客户1在排队等待付钱...
客户1在排队等待付钱...

next
通知来了,客户1付钱!
通知来了,客户2付钱! 

这也就说明每调用一次Set,只有一个线程会解除等待,换句话说,有多少个线程就要调用多少次Set,线程才会全部继续。

 

、ManualResetEvent

在AutoResetEvent中,如果要通知两个线程,则Set方法要被执行两次,也以快餐店的例子做了举例,但如果有一天客户1中彩票了,要请部门的10个同事吃饭,也就是说只要Set一次,所有在等待的线程都会解除等待,相当于收银员只收一次钱,10个人都可以通过收银去吃饭,这时我们就要用到ManualResetEvent类,它的用法和AutoResetEvent基本一样,区别就在于它是一量Set方法发出通知后,要再次阻塞的话就需要手动去修改,也就是调用Reset方法,代码如下:

 1  class Program
 2  {
 3      //若要将初始状态设置为终止,则为 true;若要将初始状态设置为非终止,则为 false。
 4      static ManualResetEvent manualResetEvent = new ManualResetEvent(false);
 5  
 6      static void Main(string[] args)
 7      {
 8          Thread t1 = new Thread(() =>
 9          {
10              Console.WriteLine("客户1在排队等待付钱...");
11  
12              //客户1调用manualResetEvent上的WaitOne来等待付钱通知
13              manualResetEvent.WaitOne();
14              Console.WriteLine("已经有人请客,客户1不用付钱");
15          });
16          t1.IsBackground = true;
17          t1.Start();
18  
19          Thread t2 = new Thread(() =>
20          {
21              Console.WriteLine("客户2在排队等待付钱...");
22  
23              //客户2调用manualResetEvent上的WaitOne来等待付钱通知
24              manualResetEvent.WaitOne();
25              Console.WriteLine("已经有人请客,客户2不用付钱!");
26          });
27          t2.IsBackground = true;
28          t2.Start();
29  
30          Pay();//发送通知
31  
32          Console.ReadKey();
33      }
34      
35      static void Pay()
36      {
37          string tip = Console.ReadLine();
38          if (tip == "next")
39          {
40              manualResetEvent.Set();//收银员发送付钱通知,通过调用Set来通知客户付钱
41          }
42      }
43  }

运行后屏幕打印:

客户1在排队等待付钱...
客户1在排队等待付钱...

next
已经有人请客,客户1不用付钱!
已经有人请客,客户2不用付钱!

如果收银员在发送通知后5秒就下班了,就不能再收钱了,这时就要把通知重置掉,让没接到通知的客户继续处于继续等待,代码如下:
 1  class Program
 2  {
 3      //若要将初始状态设置为终止,则为 true;若要将初始状态设置为非终止,则为 false。
 4      static ManualResetEvent manualResetEvent = new ManualResetEvent(false);
 5  
 6      static void Main(string[] args)
 7      {
 8          Thread t1 = new Thread(() =>
 9          {
10              Console.WriteLine("客户1在排队等待付钱...");
11  
12              //客户1调用manualResetEvent上的WaitOne来等待付钱通知
13              manualResetEvent.WaitOne();
14              Console.WriteLine("已经有人请客,客户1不用付钱");
15          });
16          t1.IsBackground = true;
17          t1.Start();
18  
19          Thread t2 = new Thread(() =>
20          {
21              Console.WriteLine("客户2在排队等待付钱...");
22  
23              Thread.Sleep(8000);//客户2发呆了8秒,这时收银员已经下班,要继续等待
24              //客户2调用manualResetEvent上的WaitOne来等待付钱通知
25              manualResetEvent.WaitOne();
26              Console.WriteLine("已经有人请客,客户2不用付钱!");
27          });
28          t2.IsBackground = true;
29          t2.Start();
30  
31          Pay();//发送通知
32  
33          Console.ReadKey();
34      }
35  
36      static void Pay()
37      {
38          string tip = Console.ReadLine();
39          if (tip == "next")
40          {
41              manualResetEvent.Set();//收银员发送付钱通知,通过调用Set来通知客户付钱
42  
43              Timer timer = new Timer(StopPay, null, 0, 5000);//5秒钟后收银员下班了,线程要重新等待了
44          }
45      }
46  
47      static void StopPay(object s)
48      {
49          manualResetEvent.Reset();
50          Console.WriteLine("收银员下班, 后面的客户要继续等待");
51      }
52  }
运行后屏幕打印:
客户1在排队等待付钱...
客户1在排队等待付钱...

next
已经有人请客,客户1不用付钱!
收银员下班,后面的客户要继续等待

总结
AutoResetEvent和ManualResetEvent的主要区别就在于:AutoResetEvent一次只能通知一个等待线程,通知后自动关闭; 而ManualResetEvent一次可通知很多个等待的线程,但要关闭需要调用Reset方法手动关闭。
posted @ 2014-04-18 08:08  刘珍宝  阅读(...)  评论(...编辑  收藏