摘自: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類並不完美,而且存在線程安全問題(代碼中用紅色字體標出),雖然實際環境概率很小。可能上面代碼多次運行都很難看到有異常發生(我多次運行未發生異常),但同時再添加幾個線程可能就會有問題了。