前些时候,在博客堂上看到有一篇关于在服务器上设定计时器,让服务器定时工作的文章,当时觉得很不错,而且为我们在服务器的后台上做一些费时的事情提供了好的解决方案。然而,这只是一个实践式的说明,没有更深入的分析用了计时器之后的一些资源、安全等实际的问题。在采用计时器之前,先看看计时器是怎样工作的,这里有一个例子(注意计时器的线程ID号):
unsafe public static void Main()
{
TmierTest();
}
private static void TmierTest()
{
System.Timers.Timer m_timer= new System.Timers.Timer();
//((System.ComponentModel.ISupportInitialize)(m_timer)).BeginInit();
m_timer.Enabled = true;
m_timer.Interval = 1100;
m_timer.Elapsed +=new ElapsedEventHandler(m_timer_Elapsed);
//((System.ComponentModel.ISupportInitialize)(m_timer)).EndInit();
for(int i=0;i<=100;i++)
{
Console.WriteLine("Main:"+i.ToString()+",Thread ID:"+AppDomain.GetCurrentThreadId().ToString());
Thread.Sleep(500);
}
}
private static void m_timer_Elapsed(object sender, ElapsedEventArgs e)
{
Console.WriteLine("Thread:"+DateTime.Now.ToString()+",Thread ID:"+AppDomain.GetCurrentThreadId().ToString());
Thread.Sleep(1000);
}
运行结果:{
TmierTest();
}
private static void TmierTest()
{
System.Timers.Timer m_timer= new System.Timers.Timer();
//((System.ComponentModel.ISupportInitialize)(m_timer)).BeginInit();
m_timer.Enabled = true;
m_timer.Interval = 1100;
m_timer.Elapsed +=new ElapsedEventHandler(m_timer_Elapsed);
//((System.ComponentModel.ISupportInitialize)(m_timer)).EndInit();
for(int i=0;i<=100;i++)
{
Console.WriteLine("Main:"+i.ToString()+",Thread ID:"+AppDomain.GetCurrentThreadId().ToString());
Thread.Sleep(500);
}
}
private static void m_timer_Elapsed(object sender, ElapsedEventArgs e)
{
Console.WriteLine("Thread:"+DateTime.Now.ToString()+",Thread ID:"+AppDomain.GetCurrentThreadId().ToString());
Thread.Sleep(1000);
}
在同一个应用程序域中,可以看到设计器的统一线程的ID号是不一样的,这就是说,计时器所采用的是一个新的线程。然而这样的测试中,我并没有清理计时器,而在调试的时候也没有报资源泄漏的问题。不解是为什么,可能是计时器的一个特点,这里就不讨论它的资源泄漏问题,而讨论一下它的线程性。
主函数与计时器函数是可以共享资源,这也很好理解,再看一个简单的例子(代码可以从上面的修改,加一个全局变量,并在主函数与线程函数上同时访问它):只给出结果,:
可以看到它们对同一个全局数据进行了非安全性的访问,为什么这么说呢,因为这里我已经认为计时器就是一个新的线程。所以,上面这种访问方式,我认为是不安全的。
然而在服务器上设定计时器就会出现这样的问题,如果计时器只做一些与IIS完全无关,且与用户请求也完全无关的事情,那么计时器的设定也就没有意义了(没有谁会那么无聊在服务器上加个没用的计时器浪费服务器资源吧)。而问题就出现在计时器的处理函数与资源共享上。再看看这样的一个例子:
//Class name :Sample
private static Sample m_object;
unsafe public static void Main()
{
TmierTest();
}
public void WriteLine(string i_msg)
{
Console.WriteLine("This is a test output. Called thread ID:"+i_msg);
}
private static void TmierTest()
{
m_object = new Sample() as Sample;
System.Timers.Timer m_timer= new System.Timers.Timer();
//((System.ComponentModel.ISupportInitialize)(m_timer)).BeginInit();
m_timer.Enabled = true;
m_timer.Interval = 1100;
m_timer.Elapsed +=new ElapsedEventHandler(m_timer_Elapsed);
//((System.ComponentModel.ISupportInitialize)(m_timer)).EndInit();
for(int i=0;i<=100;i++)
{
Console.WriteLine("Main:"+i.ToString()+",Thread ID:"+AppDomain.GetCurrentThreadId().ToString()+",M_count:"+(m_count++).ToString());
m_object.WriteLine(AppDomain.GetCurrentThreadId().ToString());
Thread.Sleep(500);
}
//m_object = null;
for(int i=0;i<=100;i++)
{
Console.WriteLine("Main:"+i.ToString()+",Thread ID:"+AppDomain.GetCurrentThreadId().ToString()+",M_count:"+(m_count++).ToString());
Thread.Sleep(500);
}
}
private static void m_timer_Elapsed(object sender, ElapsedEventArgs e)
{
Console.WriteLine("Thread:"+DateTime.Now.ToString()+",Thread ID:"+AppDomain.GetCurrentThreadId().ToString()+",M_count:"+(m_count++).ToString());
m_object.WriteLine(AppDomain.GetCurrentThreadId().ToString());
Thread.Sleep(1000);
}
运行结果:private static Sample m_object;
unsafe public static void Main()
{
TmierTest();
}
public void WriteLine(string i_msg)
{
Console.WriteLine("This is a test output. Called thread ID:"+i_msg);
}
private static void TmierTest()
{
m_object = new Sample() as Sample;
System.Timers.Timer m_timer= new System.Timers.Timer();
//((System.ComponentModel.ISupportInitialize)(m_timer)).BeginInit();
m_timer.Enabled = true;
m_timer.Interval = 1100;
m_timer.Elapsed +=new ElapsedEventHandler(m_timer_Elapsed);
//((System.ComponentModel.ISupportInitialize)(m_timer)).EndInit();
for(int i=0;i<=100;i++)
{
Console.WriteLine("Main:"+i.ToString()+",Thread ID:"+AppDomain.GetCurrentThreadId().ToString()+",M_count:"+(m_count++).ToString());
m_object.WriteLine(AppDomain.GetCurrentThreadId().ToString());
Thread.Sleep(500);
}
//m_object = null;
for(int i=0;i<=100;i++)
{
Console.WriteLine("Main:"+i.ToString()+",Thread ID:"+AppDomain.GetCurrentThreadId().ToString()+",M_count:"+(m_count++).ToString());
Thread.Sleep(500);
}
}
private static void m_timer_Elapsed(object sender, ElapsedEventArgs e)
{
Console.WriteLine("Thread:"+DateTime.Now.ToString()+",Thread ID:"+AppDomain.GetCurrentThreadId().ToString()+",M_count:"+(m_count++).ToString());
m_object.WriteLine(AppDomain.GetCurrentThreadId().ToString());
Thread.Sleep(1000);
}
请注意在TimerTest函数里注释掉的一句:m_object=null;
如果不注释这一句,后果是什么?让我很吃惊,什么事都没有!而且程序可以正常运行,只不过没有Test output的输出,这说明资源是无效的,但程序却不出任何问题,也就是说计时器一样的工作。可以想像,这样的工作已经是无效的了,然后却得不到任何的错误反馈(只在m_object=null的时候有一点点的停顿,调试也没有给出错误)。只有通过try-catch-结构可以得到错误信息,进而进行一些处理。然而这样的问题也是很严重的。
现在应该可以很清楚的认识到,如果在服务器上添加计时器其后果是什么了。
这里给出一个类的说明:
Httpcontent.Current.Request;
帮助上说的很清楚,它只能在ASP.net应该程序里使用,而且不能被继承。
如果在全局上(Global)上使用它,那么你是可以很轻松的使用它从而取得当前用户的请求,从而进行一些事件处理。
然而,如果当前用户的请求已经停止,或者用户的Session已经终止,那么计时器里的所有处理都将是无对对象的,而这样的错误几乎没有什么现象发生,因为你不可能把所有的工作都用try-catch来处理,就算是这样,也不清楚什么时候会出现这样的错误,而且出现错误后,除了清理计时器及一些资源清理外,几乎不能做其它的事情了。经过本人的多次测试(好几个星期使用服务器计时器),最后还是觉得它太不稳定了,出错机率极高,而且从错误的日志里发现它的很多错误是莫名其妙的。
最为严重的还是它的错误会严重影响IIS的正常工作,也就是说一但计时器错误,IIS的访问也会出现错误(在有资源共享的时候,如数据库的链接,这几乎是绝对的)。
经过这几个星期的测试及分析,还是觉得在服务器上使用计时器真的是一件很不划算的事情,虽然理论上可行,而且在短时间内它的处理还是不错的,但长期的效果真的很差,而且对整个应用程序的影响也是很严重的。更不用说把计时器封装在类里,这样的做法几乎是灾难性的(我就是这样做的)。
给一点错误的日志:
1/5/2006 11:02:31 PM Error in SeverForDeleteedFiles: Unspecified error
1/5/2006 11:07:52 PM Stop the WaveSystemService.
1/5/2006 11:07:52 PM System.EventArgs
1/6/2006 12:42:07 AM System.EventArgs
1/6/2006 12:47:53 AM Unspecified error
1/6/2006 12:48:06 AM Unspecified error
1/6/2006 10:01:33 AM System.EventArgs
1/6/2006 10:01:38 AM System.EventArgs
1/6/2006 10:18:53 AM Error in SeverForDeleteedFiles: Unspecified error
1/6/2006 10:35:33 AM Error in SeverForDeleteedFiles: Unspecified error
1/6/2006 10:52:13 AM Error in SeverForDeleteedFiles: Unspecified error
1/6/2006 11:08:53 AM Error in SeverForDeleteedFiles: Unspecified error
1/6/2006 11:20:26 AM System.EventArgs
1/6/2006 4:31:59 PM Do file clean up for client: 21
1/6/2006 4:48:39 PM Error in SeverForDeleteedFiles: Unspecified error
1/6/2006 5:17:09 PM System.EventArgs
1/6/2006 5:24:20 PM Stop the WaveSystemService.
简单的说明一下,在无奈之下,我也是在整个函数里使用了catch结构才得到这么一点Unspecified error信息。而与此同时,IIS的请求也会有同样的错误产生。总之,服务器上是不能用计时器了的,至少我是不敢用了。