摘自:http://www.cnblogs.com/willick/p/4177977.html 僅供參考學習
有時候你需要一個線程在接收到某個信號時,才開始執行,否則處於等待狀態,這是一種基於信號的事件機制。.NET框架提供一個ManualResetEvent類來處理這類事件,它的 WaiOne 實例方法可使當前線程一直處於等待狀態,直到接收到某個信號。它的Set方法用於打開發送信號。下面是一個信號機制的使用示例:
1 //線程的信號機制
2 #region
3 var signal = new ManualResetEvent(false);
4 DateTime beginTime = DateTime.Now;
5 new Thread(() => {
6 Console.WriteLine("waiting for signal...");
7 signal.WaitOne();
8 signal.Dispose();
9 Console.WriteLine("Got signal");
10 }).Start();
11 Thread.Sleep(2000);
12 TimeSpan ts = (DateTime.Now - beginTime);
13 Console.WriteLine("已消耗啦"+ts.TotalMilliseconds);
14 signal.Set();
15
16 Console.ReadKey();
17 #endregion
效果:
1 waiting for signal... 2 已消耗啦2003.1145 3 Got signal
當執行Set方法後,信號保持打開狀態,可通過Reset方法將其關閉,若不再需要,通過Dispose將其釋放。如果預期的等待時間很短,可以用ManualResetEventSlim代替ManualResetEvent,前者在等待時間較短時性能更好。信號機制非常有用,後面的日志案例會用到它。
線程池中的線程
線程池中的線程是有CLR來管理的,在下面的2中條件下,線程池能起到最好的效用:
*任務運行的時候比較短(<250ms),這樣CLR可以充分調配現有的空閒線程來處理該任務;
*大量時間處於等待(或阻塞)的任務不去支配線程池的線程。
1 // 方式1:Task.Run,.NET Framework 4.5 才有
2 Task.Run (() => Console.WriteLine ("Hello from the thread pool"));
3
4 // 方式2:ThreadPool.QueueUserWorkItem
5 ThreadPool.QueueUserWorkItem (t => Console.WriteLine ("Hello from the thread pool"));
案例:支持並發的異步日志組件
基於上面的知識,我們可以實現應用程序的並發寫日志日志功能。在應用程序中,寫日志是常見的功能,簡單分析一下該功能的需求:
1 public class Logger
2 {
3 /*
4 * 1.需要一個用來存放寫日志任務的隊列
5 * 2.需要有一個信號機制來標識是否有新的任務要執行
6 * 3.當有新的寫日志任務時,將該任務加入到隊列中,並發出信號
7 * 4.用一個方法來處理隊列中的任務,當接受新任務信號時,就依次調用隊列中的任務
8 * 5.lock對象要實現對入棧和出棧的鎖操作,保證出棧的時候不會有入棧的操作
9 */
10 private Queue<Action> queue;
11 private ManualResetEvent switchSignal;
12 private Thread loggingThread;
13 private static readonly Logger log = new Logger();
14 public static Logger GetIntence()
15 {
16 return log;
17 }
18 private Logger()
19 {
20 queue = new Queue<Action>();
21 switchSignal = new ManualResetEvent(false);
22 loggingThread = new Thread(ReceiveInfo);
23 loggingThread.IsBackground = true;
24 loggingThread.Start();
25 }
26 private void ReceiveInfo()
27 {
28 switchSignal.WaitOne();
29 switchSignal.Reset();
30
31 Thread.Sleep(100);
32 Queue<Action> oldQueue;
33 lock (queue)
34 {
35 oldQueue = new Queue<Action>(queue);
36 queue.Clear();
37 }
38 foreach (var action in oldQueue)
39 {
40 action();
41 }
42 }
43 //任務添加
44 public void WriteLog(string content)
45 {
46 lock (queue)//存在線程安全問題,可能發生阻塞。
47 {
48 queue.Enqueue(() => File.AppendAllText("log.txt", content));
49 }
50 switchSignal.Set();
51 }
52 public static void BeginLog(string content)
53 {
54 Task.Factory.StartNew(() => GetIntence().WriteLog(content));//4.0 不支持task.run();
55 }
56 }
1 static void Main(string[] args)
2 {
3 Thread t1 = new Thread(Working);
4 t1.Name = "Thread1";
5 Thread t2 = new Thread(Working);
6 t2.Name = "Thread2";
7 Thread t3 = new Thread(Working);
8 t3.Name = "Thread3";
9
10 // 依次啟動3個線程。
11 t1.Start();
12 t2.Start();
13 t3.Start();
14
15 Console.ReadKey();
16 }
17
18 // 每個線程都同時在工作
19 static void Working()
20 {
21 // 模擬1000次寫日志操作
22 for (int i = 0; i < 1000; i++)
23 {
24 // 異步寫文件
25 Logger.BeginLog(Thread.CurrentThread.Name + " writes a log: " + i + ", on " + DateTime.Now.ToString() + "\r\n");
26 }
27 }
28 }
通過這個示例,目的是讓大家掌握線程和並發在開發中的基本應用和要注意的問題。
遺憾的是這個Logger類並不完美,而且存在線程安全問題(代碼中用紅色字體標出),雖然實際環境概率很小。可能上面代碼多次運行都很難看到有異常發生(我多次運行未發生異常),但同時再添加幾個線程可能就會有問題了。