在我的这篇文章《.NET Framework中的计时器对象》中,讨论了几种.NET Framework定时器异同之处,以及使用场合。我当时的初忠也是在为LumaQQ.NET寻找一种最合理的线程定时执行和线程排队的方案。因为在LumaQQ中,有需要定时执行的线程,比如发送KeepLive包;有需要不同间隔时间,间隔时间是动态的且不应该是“可重入”(前一个执行还没有完成,又再次进行执行定时任务),比如重发包;有需要在一个独立线程上不定期执行的任务,比如收到包后的事件处理(收到数据包后,程序主线程需要继续去监听数据,而不能在主线程上去进行包的处理工作,这样如果事件代理的执行时间很长的话,主线程的执行周期被占用了,而造成无法及时接收和发送数据包)。在这三种情况下,直接使用Timer组件肯定无法完全满足我们的要求。
通过分析Timer组件,我们就很容易发现,他们都是通过ThreadPool这个类的功能来实现定时器的功能。ThreadPool类,提供了对当前进程内的后台线程统一管理,集中调配和资源重用的线程池实现。利用ThreadPool可以实现任务的延迟处理,I/0 异常操作,线程间的互斥执行,定时任务等功能。
ThreadPool提供了一系列管理和维护线程池的接口。在LumaQQ.NET中,我需要的功能就是利用:QueueUserWorkItem 和RegisterWaitForSingleObject 这两个接口来实现的。
QueueUserWorkItem :让我们可以注册一个排队的异步任务,等到线程池有剩余的线程资源时去异步执行任务。
RegisterWaitForSingleObject :让我们可以注册一个等待信号就绪的异步任务,等到该线程等待的信号就绪后异步执行该任务。这个接口等待信号是一个WaitHandle对象,同时还可以指定等待超时(间隔时间),而通过设置executeOnlyOnce参数的值可以指定是否重复执行该任务。
了解了ThreadPool的基本情况后,我们就可以来设计我们的异步线程执行框架。首先对异步任务和间隔任务,我们分别定义了两个接口ICallable和IRunable来描述:
1: /// <summary>
2: /// 可以被提交到线程池异步处理的接口
3: /// <remark>abu 2008-03-07 </remark>
4: /// </summary>
5: public interface ICallable
6: {
7: /// <summary>
8: /// 是否已经在运行
9: /// <remark>abu 2008-03-07 </remark>
10: /// </summary>
11: /// <value></value>
12: bool IsRunning { get; }
13: /// <summary>
14: /// WaitCallback回调
15: /// <remark>abu 2008-03-07 </remark>
16: /// </summary>
17: /// <param name="state">The state.</param>
18: void Call(object state);
19: }
20: /// <summary>
21: /// 可以被提交到线程池定时运行的接口
22: /// <remark>abu 2008-03-07 </remark>
23: /// </summary>
24: public interface IRunable : IDisposable
25: {
26: /// <summary>是否已经在运行
27: /// <remark>abu 2008-03-07 </remark>
28: /// </summary>
29: /// <value></value>
30: bool IsRunning { get; }
31: /// <summary>
32: /// <remark>abu 2008-03-07 </remark>
33: /// </summary>
34: /// <param name="state">The state.</param>
35: /// <param name="timedOut">if set to <c>true</c> [timed out].</param>
36: void Run(object state, bool timedOut);
37: /// <summary>
38: /// 注册在线程池后的信号变量
39: /// <remark>abu 2008-03-07 </remark>
40: /// </summary>
41: /// <value></value>
42: WaitHandle WaitHandler { get; set; }
43: /// <summary>
44: /// 注册后的对象
45: /// <remark>abu 2008-03-07 </remark>
46: /// </summary>
47: /// <value></value>
48: RegisteredWaitHandle RegisterdHandler { get; set; }
49: }
之后,我们需要定义一个任务注册类,来管理异步任务的注册的提交工作,这个类我将它命名为:ThreadExcutor
1: /// <summary>
2: /// 利用线程池来异步执行线程
3: /// <remark>abu 2008-03-07 </remark>
4: /// </summary>
5: public class ThreadExcutor
6: {
7: /// <summary>提交一个线程等待执行
8: /// <remark>abu 2008-03-07 </remark>
9: /// </summary>
10: /// <param name="callable">The callable.</param>
11: /// <param name="state">The state.</param>
12: public static void Submit(ICallable callable, object state)
13: {
14: if (!callable.IsRunning)
15: {
16: ThreadPool.QueueUserWorkItem(new WaitCallback(callable.Call), state);
17: }
18: }
19: /// <summary>
20: /// 注册一个轮循线程
21: /// <remark>abu 2008-03-07 </remark>
22: /// </summary>
23: /// <param name="runnable">The runnable.</param>
24: /// <param name="state">The state.</param>
25: /// <param name="interval">The interval.</param>
26: public static void RegisterIntervalObject(IRunable runnable, object state, long interval, bool onlyOnce)
27: {
28: // if (runnable.RegisterdHandler == null)
29: // {
30: runnable.WaitHandler = new AutoResetEvent(false);
31: runnable.RegisterdHandler = ThreadPool.RegisterWaitForSingleObject(runnable.WaitHandler, new WaitOrTimerCallback(runnable.Run), state, interval, onlyOnce);
32: // }
33: }
34: }
实际上这个类,只是将传入ICallable和IRunable对象,注册到线程池当中来,而运行时机则由线程池去管理和分配。接下来就是实际的任务设计了,在LumQQ.NET中目前有三个这种的任务:
PacketIncomeTrigger:异步处理输入包。只有等到有收到数据后,才提交这个任务,在后台线程中去处理QQ事件。一次处理会全部处理所有已收到的输入包,处理完后退出。当包处理线程正在运行时,试图注册相同任务将自动不会被注册。
KeepAliveTrigger:定时执行发送在线包的任务,它实现的就是Timer组件的功能。
ResendTrigger:这是一个不定时执行的间隔线程。重复发送包,保证每一次重得发送包都是顺序执行的,而不希望同时可能执行多个重复发送包线程,而且下一次的执行时间也是动态的。要实现这个功能我们只需要在注册等待线程时指定合适的超时时间和executeOnlyOnce为true(只执行一次),等到任务执行结束后,再重新注册一次定时线程。这样就可以完善解决不定时的间隔线程问题了。
更多详细的功能实现请参考LumaQQ.NET中的相关代码。