编写可扩展的轮询服务






4.43/5 (3投票s)
带可配置插件任务的 Windows 轮询服务
引言
本文介绍了如何设计和开发一个 Windows 轮询服务,该服务足够灵活,可以支持未来添加新任务。
本文主要有两个部分:任务配置以及这些任务的创建和执行。
这同时也是一个很好的教程,介绍如何使用 Configuration Section Designer 设置 **自定义配置节**,以及如何使用系统计时器类在 Windows 服务中进行轮询。
背景
在企业系统环境中,您经常需要执行异步进程/任务。最常见的方法是创建一个 Windows 服务来轮询并执行这些任务。然而,随着时间的推移,这会导致创建大量的服务。
管理多个服务并确保它们都正常运行本身就是一项艰巨的任务。更不用说每次都需要重写相同的轮询服务代码所涉及的开发时间了。这促使我构建了一个通用的轮询服务,它可以以有序的方式运行任何任务,并且足够灵活,可以轻松地添加新任务。
必备组件
您需要从 CodePlex 下载并安装 **Configuration Section Designer**。
Using the Code
第一部分将介绍如何创建一个专门用于配置任务的配置节。Configuration Section Designer 工具是一个非常方便的小工具,可以轻松完成大部分繁重的工作,我强烈推荐它。
配置节
首先,我们需要创建一个配置节来处理单个任务的配置。
创建项目后,您需要在解决方案资源管理器中右键单击项目,然后选择“添加新项”。
这将打开“添加新项”对话框。对于 Configuration Section Designer 工具的默认安装,您应该能够选择 `ConfigurationSectionDesigner` 模板,如下图所示。

设计配置节
打开设计器后,您需要通过从工具箱拖动“配置元素”图标来创建一个配置元素。右键单击设计器上新创建的配置元素,然后从上下文菜单中选择“属性”。然后,以与上面创建配置节相同的方式输入以下属性。
Name - Task
NameSpace - WSPolling
以与上面创建配置节相同的方式,将以下属性添加到 `Configuration` 元素。
* 请注意,XML 名称区分大小写。
名称 | 类型 | XML 名称 | IsKey |
TaskName |
字符串 |
taskName |
True |
TaskOrder |
Int32 |
taskOrder |
|
Enabled |
布尔值 |
enabled |
然后,我们需要以与上面相同的方式添加配置元素集合,您可以设置配置元素集合的属性。
Name - Tasks
Namespace - WSPolling
Xml Item Name - task
Item Type - Task
然后,我们需要从工具箱添加一个配置节。以与定义配置元素属性相同的方式,右键单击设计器上新创建的配置节元素,然后从上下文菜单中选择“属性”。
在“属性”窗口中,设置以下属性:
Name - TaskConfiguration
Namespace - WSPolling
XML Section Name - taskConfiguration
* 请注意,XML 节名称区分大小写。
然后,您需要创建一个配置节下的元素并设置以下属性。
Name - Tasks
Type - Tasks
Xml Name tasks
所有配置的最终结果应如下所示:

以下代码创建了一个 Windows 轮询服务,该服务加载、排序并执行各种任务及其属性。
所需类
- `ITask` – 定义单个任务的接口
- `TaskManager` – 加载、排序和执行各种单个任务的对象
- `Task1` - 任务的实现
- `Task2` - 任务的实现
创建一个系统计时器
我们使用 `System.Timer` 类以固定的时间间隔触发事件来执行 Windows 服务的轮询操作。您可以配置计时器以在任何给定时间间隔触发 - 此处设置为 4 秒。首先,我们实例化一个 `TaskManager` 类型的对象。然后,通过查找 `app.config` 文件中的配置节来加载所有启用的任务。
public void runService()
{
TaskManager tm = new TaskManager();
tm.LoadSortedProviders();
aTimer = new System.Timers.Timer(4000);
this.aTimer.AutoReset = true;
// Hook up the Elapsed event for the timer.
aTimer.Elapsed += new ElapsedEventHandler(tm.ExecuteTasks);
aTimer.Start();
GC.KeepAlive(this.aTimer);
}
TaskManager
此对象管理我们如何加载和执行任务。此对象包含两个主要方法。
`LoadSortedProviders` 方法加载 `TaskSettings` 配置节下的所有任务,然后按 `TaskOrder` 对它们进行排序。您可以在此方法中添加任何您可能需要的附加行为。
public void LoadSortedProviders()
{
try
{
foreach (Task tc in config.Tasks.OfType<Task>())
{
ITask t = Activator.CreateInstance(Type.GetType(tc.TaskName)) as ITask;
t.TaskOrder = tc.TaskOrder;
t.Enabled = tc.Enabled;
if (t.Enabled)
{
_cprovider.Add(t);
}
}
_cprovider.Sort(new TaskPriorityComparer());
}
catch (Exception ex)
{
}
}
`ExecuteTasks` 方法从排序的任务列表中执行每个任务。
public void ExecuteTasks(object source, ElapsedEventArgs e)
{
try
{
foreach (ITask t in _cprovider)
{
t.Execute();
}
}
catch (Exception ex)
{
}
}
Task1/Task 2
这些类包含实际的任务实现。您可以通过添加实现 `ITask` 接口的类,并在 `Task` 类的 `Execute` 方法中添加任务实现来扩展轮询服务。它们也需要在配置文件中进行配置,如下面的任务配置部分所述。
public void Execute( )
{
Console.WriteLine("Executing task1");
}
任务配置
每个添加的任务都需要在配置文件中进行以下配置:
`taskName` 是您的 `Task` 对象的完全限定名称,包括命名空间;`enabled` 将决定在轮询服务启动时是否运行或忽略该任务;`taskOrder` 属性将决定任务的执行顺序。您可以根据您的自定义需求添加更多属性。
<tasks>
<task taskName="WSPolling.Task1" enabled="true" taskOrder="0" />
<task taskName="WSPolling.Task2" enabled="true" taskOrder="1" />
</tasks>
关注点
我想做的一件事是尝试让这些任务在它们自己的内存空间中运行,这样它们就不会产生相互依赖。也就是说,如果其中一个崩溃了,我想看到其他任务继续运行。
`System.Timer` 类将在从 `ThreadPool` 获取的线程上执行事件。