在最近的一个项目中,经常有非常多个数据报文需要按各自的特定周期发送,而且用户可以控制是否继续发送、发送多少次、发送的时间周期等,如果每个发送作业都开一个线程,对机器的开销会特别大,而如果使用C#自带的线程池就不能很好的实现这些功能。基于此,需要自己实现一个简单的线程池。
下面这个自定义线程池的特点是,可执行多个周期性作业,每个作业可以由用户自己控制是否继续执行、加入或离开线程池、实时改变执行周期、执行次数、暂停执行和继续执行、停止线程池和重启线程池。并且对周期性作业的准时性做了优化。不足是暂时还未实现线程池的可伸缩性,线程数量是固定的。不过这只需要做较小改动就可以实现。
实现如下:
1、周期性作业类
/// <summary>
/// 时间作业的具体作业方法的委托
/// </summary>
public delegate void TaskFunc();
/// <summary>
/// 时间作业
/// <note>成员包括剩余做几次、是否做、每次作业间隔多久、最后一次作业的时间滴答数</note>
/// </summary>
public class TimeTask
{
private int _TaskID = -1;
private int _LeftCount = -1; //剩余做几次,-1表示一直做
private bool _IsDoing = true; //是否执行任务的标记
private bool _IsInPool = true; //是否在池中的标记
private long _Interval = 20000000; //时间间隔 毫微秒(10^-4ms)
private long _NextRunStartTime = 0; //下一次计划作业时间滴答数
private long _RanCount = 0; //作业已执行次数
private long _AverageRunTime = 0; //作业平均执行时间
private TaskFunc _Func; //作业方法
public int TaskID { get { return _TaskID; } set { _TaskID = value; } }
public int LeftCount { get { return _LeftCount; } set { _LeftCount = value; } }
public bool IsDoing { get { return _IsDoing; } set { _IsDoing = value; } }
public bool IsInPool { get { return _IsInPool; } set { _IsInPool = value; } }
public long Interval { get { return _Interval; } set { _Interval = value; } }
public long NextRunStartTime { get { return _NextRunStartTime; } }
public long AverageRunTime { get { return _AverageRunTime; } }
public TaskFunc Func
{
get { return _Func; }
set { _Func = value; }
}
//执行作业
public void run()
{
if (_LeftCount != 0 && _Func != null)
{
long _LastRunStartTime = DateTime.Now.Ticks;
_Func();
long _LastRunFinishTime = DateTime.Now.Ticks;
long _NowRunTime = (_LastRunFinishTime - _LastRunStartTime);
_NextRunStartTime = _LastRunFinishTime + _Interval - _NowRunTime;
_AverageRunTime = (_AverageRunTime * _RanCount + _NowRunTime) / (++_RanCount);
//System.Console.Out.WriteLine("当前耗时: " + _NowRunTime / 10000 + "ms");
//System.Console.Out.WriteLine("平均耗时: " + _AverageRunTime / 10000 + "ms");
if (_LeftCount > 0)
_LeftCount--;
}
}
//从线程池中拆卸此作业
public void dropFromPool()
{
_IsInPool = false;
}
//停止执行此任务
public void stop()
{
_IsDoing = false;
}
//重新启动此任务
public void restart()
{
_IsDoing = true;
}
//重载equal
public override bool Equals(object obj)
{
try
{
TimeTask _task = (TimeTask)obj;
return _TaskID == _task.TaskID;
}
catch (Exception) { return false; }
}
}
2、自定义线程池类
public class MyThreadPool
{
private static int _WORKNUM = 5; //线程池线程数
private static long _LITTLETIMESPAN = 50000; //小时间间隔 毫微秒(10^-4ms)
private static int _TASKOFFSETFACTOR = 3; //作业排队因子
private static List<TimeTask> _TaskList = new List<TimeTask>(); //作业队列
private static WorkThread[] _WorksThread = new WorkThread[_WORKNUM]; //线程池中的所有线程
private static bool _isRun = true; //控制线程池运行与否的标志
private static int _NextTaskID = 0; //下一个作业的ID
#region 获取单例
private static readonly MyThreadPool _instance = new MyThreadPool();
static MyThreadPool()
{
_isRun = true;
for (int _i = 0; _i < _WORKNUM; _i++)
{
_WorksThread[_i] = new WorkThread();
_WorksThread[_i].start();
}
}
private MyThreadPool()
{
_isRun = true;
for (int _i = 0; _i < _WORKNUM; _i++)
{
_WorksThread[_i] = new WorkThread();
_WorksThread[_i].start();
}
}
public static MyThreadPool Instance
{
get
{
return _instance;
}
}
#endregion
//增加作业,并提供作业ID
public void addTask(TimeTask _task)
{
//作业需要先执行一次
_task.run();
_task.TaskID = _NextTaskID++;
lock (_TaskList)
{
_TaskList.Add(_task);
}
}
//开启线程池中所有线程
public void startAllThread()
{
if (!_isRun)
{
_isRun = true;
for (int _i = 0; _i < _WORKNUM; _i++)
{
_WorksThread[_i].start();
}
}
}
//自然停止所有线程(等待所有作业单个运行完毕)
public void stopAllThread()
{
_isRun = false;
}
//清空作业列表
public void clearTaskList()
{
lock (_TaskList)
{
_TaskList.Clear();
}
}
//删除某一项作业
public bool removeTask(int _TaskID)
{
lock (_TaskList)
{
foreach(TimeTask _iter in _TaskList)
{
if (_iter.TaskID == _TaskID)
return _TaskList.Remove(_iter);
}
}
return false;
}
/// <summary>
/// 线程池中的单个线程
/// <note>MyThreadPool的内部类</note>
/// </summary>
private class WorkThread
{
//线程实体
private Thread _thisThread;
public WorkThread()
{
}
//开启线程
public void start()
{
if (_thisThread == null)
{
_thisThread = new Thread(_job);
_thisThread.Start();
return;
}
if (_thisThread.IsAlive) { }
else
{
_thisThread = new Thread(_job);
_thisThread.Start();
return;
}
}
//线程执行方法:取出作业并执行,执行完毕后再次放回作业
private void _job()
{
//判断线程池是否要运行
while (_isRun)
{
TimeTask _nowTask = null;
//取出列表中第一个任务
lock (_TaskList)
{
if (_TaskList.Count != 0)
{
_nowTask = _TaskList.First();
_TaskList.RemoveAt(0);
}
}
//如果作业队列为空,则挂起线程100ms后重新取作业
if (_nowTask == null) { Thread.Sleep((int)(_LITTLETIMESPAN/10000));continue; }
else
{
//拆卸作业,取出作业不往队列中再次添加
if (!_nowTask.IsInPool)
{
//
}
//剩余作业次数不为0且作业标记为执行
else if (_nowTask.LeftCount != 0 && _nowTask.IsDoing)
{
//如果当前时间小于计划执行时间的负误差,就将作业排到队列末尾
if (DateTime.Now.Ticks < _nowTask.NextRunStartTime - _LITTLETIMESPAN)
{
lock (_TaskList)
{
_TaskList.Add(_nowTask);
}
}
//如果当前时间大于等于计划执行时间,就执行作业,然后将作业排到队列末尾
else if (DateTime.Now.Ticks >= _nowTask.NextRunStartTime)
{
_nowTask.run();
lock (_TaskList)
{
_TaskList.Add(_nowTask);
}
}
//如果当前时间处于计划执行时间的负误差之中,就将作业排到队列中靠前的位置(取决于_TASKOFFSETFACTOR)
else
{
lock (_TaskList)
{
if (_TaskList.Count < _WORKNUM)
{
_TaskList.Add(_nowTask);
}
else
{
_TaskList.Insert(_TaskList.Count / _TASKOFFSETFACTOR, _nowTask);
}
}
}
}
//如果当前作业剩余执行次数为零或者作业标记为不执行,就将此作业排到队列末尾
else
{
lock (_TaskList)
{
_TaskList.Add(_nowTask);
}
}
}
}
}
}
}
3、测试结果总结:
编写了一个小Demo对此线程池进行了测试,测试使用了10个周期性作业、每个作业的执行周期和执行时间都不相同。
以下是测试数据:(数据项从左至右依次为作业开始执行时间、作业名称、作业模拟执行时间【调用sleep】,作业名称的数字位也代表执行周期,如Task5代表此作业5秒执行一次)
12:23:06:903: Task1 --> 50ms
12:23:06:906: Task2 --> 20ms
12:23:06:908: Task3 --> 500ms
12:23:06:909: Task4 --> 200ms
12:23:06:911: Task5 --> 10ms
12:23:06:912: Task6 --> 30ms
12:23:06:914: Task7 --> 100ms
12:23:06:916: Task8 --> 40ms
12:23:06:918: Task9 --> 60ms
12:23:06:920: Task10 --> 70ms
12:23:08:66: Task1 --> 50ms
12:23:09:67: Task1 --> 50ms
12:23:09:155: Task2 --> 20ms
12:23:10:312: Task1 --> 50ms
12:23:10:348: Task3 --> 500ms
12:23:11:235: Task4 --> 200ms
12:23:11:312: Task1 --> 50ms
12:23:11:486: Task2 --> 20ms
12:23:12:38: Task5 --> 10ms
12:23:12:312: Task1 --> 50ms
12:23:12:986: Task6 --> 30ms
12:23:13:410: Task1 --> 50ms
12:23:13:950: Task3 --> 500ms
12:23:14:386: Task7 --> 100ms
12:23:14:409: Task1 --> 50ms
12:23:14:842: Task2 --> 20ms
12:23:15:231: Task8 --> 40ms
12:23:15:409: Task1 --> 50ms
12:23:15:429: Task4 --> 200ms
12:23:16:16: Task9 --> 60ms
12:23:16:480: Task1 --> 50ms
12:23:16:923: Task2 --> 20ms
12:23:16:950: Task3 --> 500ms
12:23:17:126: Task10 --> 70ms
12:23:17:415: Task5 --> 10ms
12:23:17:516: Task1 --> 50ms
12:23:18:750: Task1 --> 50ms
12:23:18:985: Task6 --> 30ms
12:23:19:490: Task2 --> 20ms
12:23:19:578: Task4 --> 200ms
12:23:19:988: Task3 --> 500ms
发现作业执行的周期准时性存在较小的误差,但都保持在毫秒级,满足当前项目的需求。