线程的信号机制

摘自: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. 在后台异步执行,和其它线程互不影响。
      根据上文线程池的两个最优使用条件,由写日志线程会长时间处于阻塞(或运行等待)状态,所以它不适合使用线程池。即不能使用Task.Run,而最好使用new Thread。

    2. 支持并发,即多个任务(分布在不同线程上)可同时调用写日志功能,但需保证线程安全。
      支持并发,必然要用到锁,但要完全保证线程安全,那就要想办法避免“死锁”。只要我们把“上锁”的操作始终由同一个线程来做即可避免“死锁”问题,但这样的话,并发请求的任务只能放在队列中由该线程依次执行(因为是后台执行,无需即时响应用户,所以可以这么做)。

    3. 单个实例,单个线程。
      任何地方调用写日志功能都调用的是同一个Logger实例(显然不能每次写日志都新建一个实例),即需使用单例模式。不管有多少任务调用写日志功能,都必须始终使用同一个线程来处理这些写日志操作,以保证不占用过多的线程资源和避免新建线程带来的延迟。

   

 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() + "
");
26             }
27         }
28     }

通过这个示例,目的是让大家掌握线程和并发在开发中的基本应用和要注意的问题。

遗憾的是这个Logger类并不完美,而且存在线程安全问题(代码中用红色字体标出),虽然实际环境概率很小。可能上面代码多次运行都很难看到有异常发生(我多次运行未发生异常),但同时再添加几个线程可能就会有问题了。

原文地址:https://www.cnblogs.com/meiCode/p/4700123.html