您的位置:首页 > 编程语言 > C#

C#的Timer

2015-09-20 00:37 561 查看
再C#里现在有3个Timer类:

System.Windows.Forms.Timer

System.Threading.Timer

System.Timers.Timer

这三个Timer我想大家对System.Windows.Forms.Timer已经很熟悉了,唯一我要说的就是这个Timer在激发Timer.Tick事件的时候,事件的处理函数是在程序主线程上执行的,所以在WinForm上面用这个Timer很方便,因为在From上的所有控件都是在程序主线程上创建的,那么在Tick的处理函数中可以对Form上的所有控件进行操作,不会造成WinForm控件的线程安全问题。

1、Timer运行的核心都是System.Threading.ThreadPool

在这里要提到ThreadPool(线程池)是因为,System.Threading.Timer 和System.Timers.Timer运行的核心都是线程池,Timer每到间隔时间后就会激发响应事件,因此要申请线程来执行对应的响应函数,Timer将获取线程的工作都交给了线程池来管理,每到一定的时间后它就去告诉线程池:“我现在激发了个事件要运行对应的响应函数,麻烦你给我向操作系统要个线程,申请交给你了,线程分配下来了你就运行我给你的响应函数,没分配下来先让响应函数在这儿排队(操作系统线程等待队列)”,消息已经传递给线程池了,Timer也就不管了,因为它还有其他的事要做(每隔一段时间它又要激发事件),至于提交的请求什么时候能够得到满足,要看线程池当前的状态:

1、如果线程池现在有线程可用,那么申请马上就可以得到满足,有线程可用又可以分为两种情况:

<1>线程池现在有空闲线程,现在马上就可以用

<2>线程池本来现在没有线程了,但是刚好申请到达的时候,有线程运行完毕释放了,那么申请就可以用别人释放的线程。

这两种情况情况就如同你去游乐园玩赛车,如果游乐园有10辆车,现在有3个人在玩,那么还剩7辆车,你去了当然可以选一辆开。另外还有一种情况就是你到达游乐园前10辆车都在开,但是你运气很好,刚到游乐园就有人不玩了,正好你坐上去就可以接着开。

2、如果现在线程池现在没有线程可用,也分为两种情况:

<1>线程池现有线程数没有达到设置的最大工作线程数,那么隔半秒钟.net framework就会向操作系统申请一个新的线程(为避免向线程分配不必要的堆栈空间,线程池按照一定的时间间隔创建新的空闲线程。该时间间隔目前为半秒,但它在 .NET Framework 的以后版本中可能会更改)。

<2>线程池现有工作线程数达到了设置的最大工作线程数,那么申请只有在等待队列一直等下去,直到有线程执行完任务后被释放。

那么上面提到了线程池有最大工作线程数,其实还有最小空闲线程数,那么这两个关键字是什么意思呢:

1、最大工作线程数:实际上就是指的线程池能够向操作系统申请的最大线程数,这个值在.net framework中有默认值,这个默认值是根据你计算机的配置来的,当人你可以用ThreadPool.GetMaxThreads返回线程池当前最大工作线程数,你也可以同ThreadPool.SetMaxThreads设置线程池当前最大工作线程数。

2、最小空闲线程数:是指在程序开始后,线程池就默认向操作系统要最小空闲线程数个线程,另外这也是线程池维护的空闲线程数(如果线程池最小空闲线程数为3,当前因为一些线程执行完任务被释放,线程池现在实际上有10个空闲线程,那么线程池会让操作系统释放多余的7个线程,而只维持3个空闲线程供程序使用),因为上面说了,在执行程序的时候在要线程池申请线程有半秒的延迟时间,这也会影响程序的性能,所以把握好这个值很重要,用样你可以用ThreadPool.GetMinThreads返回线程池当前最小空闲线程数,你也可以同ThreadPool.SetMinThreads设置线程池当前最小空闲线程数。

下面是我给的例子,这个例子让线程池申请800个线程,其中设置最大工作线程数为500,800个线程任务每个都要执行100000000毫秒目的是让线程不会释放,并且让用户选择,是否预先申请500个空闲线程免受那半秒钟的延迟时间,其结果可想而知当线程申请到500的时候,线程池达到了最大工作线程数,剩余的300个申请进入漫长的等待时间:

代码

/***************************************************
* 项目:测试线程池
* 描述:验证线程池的最大工作线程数和最小空闲线程数
* 作者:@PowerCoder
* 日期:2010-2-22
***************************************************/

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace ConsoleApplication1
{
class Program
{
static int i=1;
static int MaxThreadCount = 800;

static void OutPut(object obj)
{
Console.Write("\r申请了:{0}个工作线程",i);
i++;
Thread.Sleep(100000000);//设置一个很大的等待时间,让每个申请的线程都一直执行
}

static void Main(string[] args)
{
int j;

Console.Write("是否先申请500个空闲线程以保证前500个线程在线程池中开始就有线程用(Y/N)?");//如果这里选择N,那么前两个任务是用的线程池默认空闲线程(可以用ThreadPool.GetMinThreads得到系统默认最小空闲线程数为2)申请立即得到满足,然而由于每个线程等待时间非常大都不会释放当前自己持有的线程,因此线程池中已无空闲线程所用,后面的任务需要在线程池中申请新的线程,那么新申请的每个线程在线程池中都要隔半秒左右的时间才能得到申请(原因请见下面的注释)
string key = Console.ReadLine();
if(key.ToLower()=="y")
ThreadPool.SetMinThreads(500, 10);//设置最大空闲线程为500,就好像我告诉系统给我预先准备500个线程我来了就直接用,因为这样就不用现去申请了,在线程池中每申请一个新的线程.NET Framework 会安排一个间隔时间,目前是半秒,以后的版本MS有可能会改

int a, b;
ThreadPool.GetMaxThreads(out a,out b);
Console.WriteLine("线程池默认最大工作线程数:" + a.ToString() + " 默认最大异步 I/O 线程数:" + b.ToString());
Console.WriteLine("需要向系统申请" + MaxThreadCount.ToString()+"个工作线程");

for (j = 0; j <= MaxThreadCount-1; j++)//由于ThreadPool.GetMaxThreads返回的默认最大工作线程数为500(这个值要根据你计算机的配置来决定),那么向线程池申请大于500个线程的时候,500之后的线程会进入线程池的等待队列,等待前面500个线程某个线程执行完后来唤醒等待队列的某个线程
{
ThreadPool.QueueUserWorkItem(new WaitCallback(OutPut));
Thread.Sleep(10);
}

Console.ReadLine();
}
}
}

代码

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Diagnostics;

namespace ConsoleApplication1
{
class UnSafeTimer
{
static int i = 0;
static System.Threading.Timer timer;
static object mylock = new object();
static int sleep;
static bool flag;
public static Stopwatch sw = new Stopwatch();

static void Excute(object obj)
{
Thread.CurrentThread.IsBackground = false;
int c;

lock (mylock)
{
i++;
c = i;
}

if (c == 80)
{
timer.Dispose();//执行Dispose后Timer就不会再申请新的线程了,但是还是会给Timmer已经激发的事件申请线程
sw.Stop();
}

if (c < 80)
Console.WriteLine("Now:" + c.ToString());
else
{
Console.WriteLine("Now:" + c.ToString()+"-----------Timer已经Dispose耗时:"+sw.ElapsedMilliseconds.ToString()+"毫秒");
}

if (flag)
{
Thread.Sleep(sleep);//模拟花时间的代码
}
else
{
if(i<=80)
Thread.Sleep(sleep);//前80次模拟花时间的代码
}
}

public static void Init(int p_sleep,bool p_flag)
{
sleep = p_sleep;
flag = p_flag;
timer = new System.Threading.Timer(Excute, null, 0, 10);
}
}

class SafeTimer
{
static int i = 0;
static System.Threading.Timer timer;

static bool flag = true;
static object mylock = new object();

static void Excute(object obj)
{
Thread.CurrentThread.IsBackground = false;

lock (mylock)
{
if (!flag)
{
return;
}

i++;

if (i == 80)
{
timer.Dispose();
flag = false;
}
Console.WriteLine("Now:" + i.ToString());
}

Thread.Sleep(1000);//模拟花时间的代码
}

public static void Init()
{
timer = new System.Threading.Timer(Excute, null, 0, 10);
}
}

class Program
{
static void Main(string[] args)
{
Console.Write("是否使用安全方法(Y/N)?");
string key = Console.ReadLine();
if (key.ToLower() == "y")
SafeTimer.Init();
else
{
Console.Write("请输入Timmer响应事件的等待时间(毫秒):");//这个时间直接决定了前80个任务的执行时间,因为等待时间越短,每个任务就可以越快执行完,那么80个任务中就有越多的任务可以用到前面任务执行完后释放掉的线程,也就有越多的任务不必去线程池申请新的线程避免多等待半秒钟的申请时间
string sleep = Console.ReadLine();
Console.Write("申请了80个线程后Timer剩余激发的线程请求是否需要等待时间(Y/N)?");//这里可以发现选Y或者N只要等待时间不变,最终Timer激发线程的次数都相近,说明Timer的确在执行80次的Dispose后就不再激发新的线程了
key = Console.ReadLine();
bool flag = false;
if (key.ToLower() == "y")
{
flag = true;
}

UnSafeTimer.sw.Start();
UnSafeTimer.Init(Convert.ToInt32(sleep), flag);
}

Console.ReadLine();
}
}
}

代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Timers;
using System.Threading;

namespace ConsoleApplication2
{
class UnSafeTimer
{
static int i = 0;
static System.Timers.Timer timer;
static object mylock = new object();

public static void Init()
{
timer = new System.Timers.Timer(10);
timer.Elapsed += new ElapsedEventHandler(timer_Elapsed);
timer.Start();
}

static void timer_Elapsed(object sender, ElapsedEventArgs e)
{
Thread.CurrentThread.IsBackground = false;
int c;

lock (mylock)
{
i++;
c = i;
}

Console.WriteLine("Now:" + i.ToString());

if (c == 80)
{
timer.Stop();//可应看到System.Timers.Timer的叫停机制比System.Threading.Timer好得多,就算在不安全的代码下Timer也最多多执行一两次(我在试验中发现有时会执行到81或82),说明Stop方法在设置Timer的Enable为false后不仅让Timer不再激发响应事件,还取消了线程池等待队列中等待获得线程的任务,至于那多执行的一两次任务我个人认为是Stop执行过程中会耗费一段时间才将Timer的Enable设置为false,这段时间多余的一两个任务就获得了线程开始执行
}

Thread.Sleep(1000);//等待1000毫秒模拟花时间的代码,注意:这里的等待时间直接决定了80(由于是不安全模式有时会是81或82、83)个任务的执行时间,因为等待时间越短,每个任务就可以越快执行完,那么80个任务中就有越多的任务可以用到前面任务执行完后释放掉的线程,也就有越多的任务不必去线程池申请新的线程避免多等待半秒钟的申请时间
}
}

class SafeTimer
{
static int i = 0;
static System.Timers.Timer timer;

static bool flag = true;
static object mylock = new object();

public static void Init()
{
timer = new System.Timers.Timer(10);
timer.Elapsed += new ElapsedEventHandler(timer_Elapsed);
timer.Start();
}

static void timer_Elapsed(object sender, ElapsedEventArgs e)
{
Thread.CurrentThread.IsBackground = false;

lock (mylock)
{
if (!flag)
{
return;
}
i++;

Console.WriteLine("Now:" + i.ToString());

if (i == 80)
{
timer.Stop();
flag = false;
}
}

Thread.Sleep(1000);//同UnSafeTimer
}

class Program
{
static void Main(string[] args)
{
Console.Write("是否使用安全Timer>(Y/N)?");
string Key = Console.ReadLine();

if (Key.ToLower() == "y")
SafeTimer.Init();
else
UnSafeTimer.Init();

Console.ReadLine();
}
}
}
}

这个例子和System.Threading.Timer差不多,这里也分为:安全类SafeTimer和不安全类UnSafeTimer,原因是 timer.Stop()有少许的延迟时间有时任务会执行到81~83,但是就算是不安全方法也就最多多执行几次,不像System.Threading.Timer多执行上百次...

所以我这里还是推荐大家使用System.Timers.Timer
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: