基础才是重中之重~关于ThreadStatic和Quartz的一点渊源
ThreadStatic
ThreadStatic是C#里的一个特性,它可以让你的字段在一个线程里有效,但你不能控制这个字段在何时被回收,即如果声明一个int32的字段为ThreadStatic,然后你为它赋值时为100,那么它什么被恢复成默认值0,我们不得而知,这在开发时,我们可能只有手动将它设为0才行,比较难看,但也没办法,谁让咱们用了ThreadStatic呢,被声明为ThreadStatic之后,已经证明这个字段是静态化的,只不过它是被局限在一个线程内的。
Quartz
Quartz是一个任务调度框架,起源于java,它目前被广泛的使用在各种后台处理数据的场合,像一些统计数据,推送数据,消息数据等,它可以大大降低前端服务器的并发压力,并且Quartz的管理界面也有很多,直接nuget安装即可,在这些产品中最知名的应该就是CrystalQuartz了,它可以在WEB界面中管理咱们的JOB项目!
日志系统Lind.DDD.Logger
Logger本来是Lind框架的一个日志组件,它是最低层的组件,是其它组件的基础,也被用到其它的业务系统里,而其中一个Quartz组件里,使用Logger时提出了一个问题,就是如何根据job去自动建立日志目录,让每个JOB都有自己的目录,这样在分析日志时还是很有必要的。
希望看到的结果如图
测试用的两个Job
public class Hello_Job : JobBase { protected override void ExcuteJob() { Console.WriteLine("Hello Job方法:" + Thread.CurrentThread.ManagedThreadId); Lind.DDD.Logger.LoggerFactory.Instance.Logger_Info("Hello Job日志!"); } } public class Hi_Job : Lind.DDD.QuartzJob.JobBase { protected override void ExcuteJob() { Console.WriteLine("Hi Job!" + Thread.CurrentThread.ManagedThreadId); Lind.DDD.Logger.LoggerFactory.Instance.Logger_Info("Hi Job!"); } }
JobBase做于所有Job的基类存在,它主要有自己的抽象方法和IJob的接口方法,其中抽象方法由字类Job自己去实现,去实现自己的业务逻辑;而IJob方法由Quartz框架去调用,并在方法中自己调用了抽象方法的内容,大致代码如下
[DisallowConcurrentExecution()] public abstract class JobBase : IJob { #region IJob 成员 /// <summary> /// Job主方法 /// </summary> /// <param name="context"></param> public void Execute(IJobExecutionContext context) { Lind.DDD.Logger.LoggerFactory.Instance.SetPath(this.GetType().Name); ExcuteJob(); Console.WriteLine(DateTime.Now.ToString() + "{0}这个Job开始执行", context.JobDetail.Key.Name); } #endregion /// <summary> /// Job具体类去实现自己的逻辑 /// </summary> protected abstract void ExcuteJob(); }
日志组件中的字段使用了ThreadStatic
对日志文件分文件夹存储,主要在日志组件中使用ThreadStatic来实现的,代码主要如下
/// <summary> /// 每个子类初始时都执行基类这个构造,初始化当前路径 /// </summary> public LoggerBase() { FileUrl = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "LoggerDir"); } /// <summary> /// 日志文件地址 /// 优化级为mvc方案地址,网站方案地址,console程序地址 /// </summary> [ThreadStatic] static protected string FileUrl;
#region ILogger 成员 public void SetPath(string path) { if (!string.IsNullOrWhiteSpace(path)) { FileUrl = FileUrl + "\\" + path; } } #endregion
对于FileLogger这个文件日志实现类来说,它要做的是,在写完文件流之后,要把FileUrl这个字段从新赋值,因为我们不知道这个字符串什么时候被清空!
lock (objLock)//防治多线程读写冲突 { using (System.IO.StreamWriter srFile = new System.IO.StreamWriter(filePath, true)) { srFile.WriteLine(string.Format("{0}{1}{2}" , DateTime.Now.ToString().PadRight(20) , ("[ThreadID:" + Thread.CurrentThread.ManagedThreadId.ToString() + "]").PadRight(14) , message)); srFile.Close(); srFile.Dispose(); } } //清除当前的路径 FileUrl = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "LoggerDir");
上面的问题,我也是找了很久,因为总是找不到测试不成功的原因,最后想到了ThreadStatic特性的声明周期,算是找到根源了,呵呵!
建议大家看看C#的《对象的生与死》!