短信平台记录日志模块,是通过异步方式来记录的,即日志工具类里初始化一个Queue对象,公共的写日志方法的处理逻辑是把日志消息放到Queue里。构造器里设定一个死循环,不停的读队,然后把日志消息持久化到磁盘的文本文件里。
构造方法的代码如下:
public Log() { //开启线程负责将日志写入到指定地点 ThreadPool.QueueUserWorkItem(o => { try { while (true) { if (_messageQueue.Count > 0) { lock (_messageQueue) { while (_messageQueue.Count > 0) { LogMessage logMessage = _messageQueue.Dequeue(); WriteFile(logMessage); } } } else { Thread.Sleep(300); } } } catch (Exception ex) { LogMessage log = new LogMessage() { Content = "构造器执行报错:" + ex.ToString() }; FileHelper.WriteFile(string.Format(@"{0}\{1:yyyy-MM-dd}-LOGERROR.txt", _logPath, DateTime.Now), log.GetFormattedLog()); } }); }
近期通过看线上站点日志,发现不定期会有ThreadAbortException被捕获,
时间:2016-08-02 10:23:01 构造器执行报错:System.Threading.ThreadAbortException: 正在中止线程。 在 System.Threading.Thread.SleepInternal(Int32 millisecondsTimeout) 在 Common.Log.<.ctor>b__1(Object o) ------------------------------------------------------------------------------- 时间:2016-08-02 10:47:32 构造器执行报错:System.Threading.ThreadAbortException: 正在中止线程。 在 System.Threading.Thread.SleepInternal(Int32 millisecondsTimeout) 在 Common.Log.<.ctor>b__2(Object o) ----------------------------------------------------------------------------------------
通过分析,当站点应用程序池回收或遇到其他未捕获的异常时,线程会被Abort,这时,当线程里的代码再被执行时,就会抛出ThreadAbortException异常。
微软官方对ThreadAbortException的介绍:
在调用 Abort 方法以销毁线程时,CLR将引发ThreadAbortException。ThreadAbortException 是一种可捕获的特殊异常,但在 catch 块的结尾处它将自动被再次引发。引发此异常时,运行库将在结束线程前执行所有 finally 块。由于线程可以在 finally 块中执行未绑定计算,或调用 Thread.ResetAbort 来取消中止,所以不能保证线程将完全结束。如果您希望一直等到被中止的线程结束,可以调用 Thread.Join 方法。Join 是一个模块化调用,它直到线程实际停止执行时才返回。
下面的示例说明如何中止线程。接收 ThreadAbortException 的线程使用 ResetAbort 方法取消中止请求并继续执行。
using System; using System.Threading; using System.Security.Permissions; public class ThreadWork { public static void DoWork() { try { for(int i=0; i<100; i++) { Console.WriteLine("Thread - working."); Thread.Sleep(100); } } catch(ThreadAbortException e) { Console.WriteLine("Thread - caught ThreadAbortException - resetting."); Console.WriteLine("Exception message: {0}", e.Message); Thread.ResetAbort(); } Console.WriteLine("Thread - still alive and working."); Thread.Sleep(1000); Console.WriteLine("Thread - finished working."); } } class ThreadAbortTest { public static void Main() { ThreadStart myThreadDelegate = new ThreadStart(ThreadWork.DoWork); Thread myThread = new Thread(myThreadDelegate); myThread.Start(); Thread.Sleep(100); Console.WriteLine("Main - aborting my thread."); myThread.Abort(); myThread.Join(); Console.WriteLine("Main ending."); } }
运行结果:
Thread - working. Main - aborting my thread. Thread - caught ThreadAbortException - resetting. Exception message: Thread was being aborted. Thread - still alive and working. Thread - finished working. Main ending.
————————————————————–我是萌萌哒分界线————————————————————
生产者/消费者模式
我们系统里的日志Helper类用到了Queue来实现异步记日志,提高系统容错和性能。现在想来,这就是一个生产者/消费者模式的运用。
在分布式系统中,这个模式更适用,尤其是消息处理。 RabbitMQ正是基于这种需求而产生的消息中间件。