学习 Monitor使用
准备类:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
public class OperationItem { public int Num { get; set; } public bool HasInit { get; set; } public void Add(int number) { Num += number; Debug.Print("{0}| add......{1}", DateTime.Now.ToString("hh:mm:ss fff"), Num); Thread.Sleep(1000); } public void Init(int number) { if (this.HasInit) return; Num = number; Debug.Print("{0}| init......{1}", DateTime.Now.ToString("hh:mm:ss fff"), Num); Thread.Sleep(1000); this.HasInit = true; } }
场合1:必须先执行Init方法,后执行add方法,init和add在不同的线程中。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
OperationItem item = new OperationItem(); private void button1_Click(object sender, RoutedEventArgs e) { Thread thread1 = new Thread(new ThreadStart(InitOperation)); thread1.Name = "thread1"; thread1.Start(); Thread thread2 = new Thread(new ThreadStart(AddOperation)); thread2.Name = "thread2"; thread2.Start(); } void AddOperation() { Monitor.Enter(item); item.Add(200); Monitor.Exit(item); } void InitOperation() { Monitor.Enter(item); item.Init(100); Monitor.Exit(item); }
或者使用lock更清爽
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
OperationItem item = new OperationItem(); private void button1_Click(object sender, RoutedEventArgs e) { Thread thread1 = new Thread(new ThreadStart(InitOperation)); thread1.Name = "thread1"; thread1.Start(); Thread thread2 = new Thread(new ThreadStart(AddOperation)); thread2.Name = "thread2"; thread2.Start(); } void AddOperation() { lock (item) { item.Add(200); } } void InitOperation() { lock (item) { item.Init(100); } }
看看日志:
02:45:27 231| init......100
The thread 'thread1' (0x17b8) has exited with code 0 (0x0).
02:45:28 237| add......300
The thread 'thread2' (0x1110) has exited with code 0 (0x0).
如果没有lock或者Monitor
02:46:36 152| add......300
02:46:36 152| init......300
The thread 'thread2' (0x1410) has exited with code 0 (0x0).
The thread 'thread1' (0x15a8) has exited with code 0 (0x0).
这不乱套了吗,所以还是lock吧。
场合2:Init方法有可能被多次调用,但是需求决定Init只能执行一次。
init方法本身要加上判断,另外线程里面也要控制。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
private void button2_Click(object sender, RoutedEventArgs e) { Thread thread1 = new Thread(new ParameterizedThreadStart(InitOperation2)); thread1.Name = "thread1"; thread1.Start(200); Thread thread2 = new Thread(new ParameterizedThreadStart(InitOperation2)); thread2.Name = "thread2"; thread2.Start(500); } void InitOperation2(object data) { if (data == null) return; int ret = 0; Int32.TryParse(data.ToString(), out ret); //Monitor.Enter(item); //item.Init(ret); //Monitor.Exit(item); lock (item) { item.Init(ret); } }
日志:
02:50:46 234| init......200
The thread 'thread1' (0x3fc) has exited with code 0 (0x0).
The thread 'thread2' (0x93c) has exited with code 0 (0x0).
如果没有lock,悲剧了:
02:51:05 631| init......500
02:51:05 631| init......500
The thread 'thread1' (0x8e0) has exited with code 0 (0x0).
The thread 'thread2' (0xa04) has exited with code 0 (0x0).
场合3:对于耗时操作,我们的耐心是有限的,设置等待超时。
1 private void button3_Click(object sender, RoutedEventArgs e) 2 { 3 Thread thread1 = new Thread(new ParameterizedThreadStart(InitOperation3)); 4 thread1.Name = "thread1"; 5 thread1.Start(200); 6 7 Thread thread2 = new Thread(new ParameterizedThreadStart(InitOperation3)); 8 thread2.Name = "thread2"; 9 thread2.Start(500); 10 } 11 12 void InitOperation3(object data) 13 { 14 if (data == null) return; 15 int ret = 0; 16 Int32.TryParse(data.ToString(), out ret); 17 18 if (Monitor.TryEnter(item, 1200)) 19 { 20 item.Add(ret); 21 Monitor.Exit(item); 22 } 23 }
留意第18行的1200,意思是等待的时间是1200毫秒,如果超出1200毫秒,就不执行了。
Add方法执行时,
线程会sleep1000毫秒,所以如果可以等待1200毫秒,还有戏,
1200ms 执行日志:
02:59:24 214| add......200
The thread 'thread1' (0x1bb8) has exited with code 0 (0x0).
02:59:25 220| add......700
The thread 'thread2' (0x1b40) has exited with code 0 (0x0).
如果只能等待800毫秒,那就没戏了,thread2直接退出。
800ms 执行日志:
03:00:11 219| add......200
The thread 'thread2' (0x9cc) has exited with code 0 (0x0).
The thread 'thread1' (0x1b2c) has exited with code 0 (0x0).
场合4:虽然可以 加锁,但是有时候还是需要交叉执行。
解释一下:
a.线程1执行add方法,执行前,要确保已经执行了Init,所以必须等待Init执行完成
b.线程2执行Init方法,尽管启动时间晚了两秒,但是启动后发现item的锁被让出,可以执行,所以马上Pulse,把Init的事情做了。
c.在线程2 Pulse的时候,线程1已经知道了线程有动静了,所以准备出动。
d.线程2 wait后,线程1马上拿过指挥权,继续完成它的工作。
1 private void button4_Click(object sender, RoutedEventArgs e) 2 { 3 Thread thread1 = new Thread(new ParameterizedThreadStart(InitOperation4)); 4 thread1.Name = "thread1"; 5 thread1.Start(200); 6 Debug.Print("{0}| start......{1}", DateTime.Now.ToString("hh:mm:ss fff"), thread1.Name); 7 Thread.Sleep(2000); 8 9 Thread thread2 = new Thread(new ParameterizedThreadStart(InitOperation4_1)); 10 thread2.Name = "thread2"; 11 Debug.Print("{0}| start......{1}", DateTime.Now.ToString("hh:mm:ss fff"), thread2.Name); 12 thread2.Start(500); 13 } 14 15 void InitOperation4(object data) 16 { 17 if (data == null) return; 18 int ret = 0; 19 Int32.TryParse(data.ToString(), out ret); 20 21 lock (item) 22 { 23 Monitor.Wait(item); 24 item.Add(ret); 25 Monitor.Pulse(item); 26 } 27 Debug.Print("{0}| finish ......{1}", DateTime.Now.ToString("hh:mm:ss fff"), "InitOperation4"); 28 } 29 30 void InitOperation4_1(object data) 31 { 32 if (data == null) return; 33 int ret = 0; 34 Int32.TryParse(data.ToString(), out ret); 35 36 lock (item) 37 { 38 Monitor.Pulse(item); 39 item.Init(ret); 40 Monitor.Wait(item); 41 } 42 Debug.Print("{0}| finish ......{1}", DateTime.Now.ToString("hh:mm:ss fff"), "InitOperation4_1"); 43 }
所以,日志是:
03:05:35 215| start......thread1
03:05:37 242| start......thread2
03:05:37 246| init......500
03:05:38 247| add......700
03:05:39 249| finish ......InitOperation4
The thread 'thread1' (0xa84) has exited with code 0 (0x0).
03:05:39 249| finish ......InitOperation4_1
The thread 'thread2' (0x1310) has exited with code 0 (0x0).
结束:
Monitor真是个好东西呀,欢迎拍砖!
Demo在这里: https://files.cnblogs.com/xiaokang088/WpfMonitor.zip
另外,这位仁兄的解释更加清晰:http://hi.baidu.com/luoyuonline/blog/item/b10dfbeb56cd35d8d539c9ac.html