昨天,领导开会说起了一件事,做研发的不能光靠记忆,一定要学会总结,做笔记,想来也是,确实一些以前遇到过的问题再出现在面前还要去百度,于是就有了现在这篇随笔。
背景:我们公司是做概预算服务行业的,现在正是从客户端转向云端的起步,原来的一些工程数据处理是通过C++本地客户端处理的,现在需要将这些数据进行分析,但是数据内容是由C++项目组生成的特定格式的文件,需要调用他们编译的动态库去解析成通过对xml文件,在我们web平台中需要将特定的格式文件进行上传,解析(需要动态库),读xml存入数据库三个操作。其中第二个步骤听同事说不能实例多个对象进行同时处理,几百兆的文件可能需要一两分钟,此时如果又有文件进入解析,服务器就会出错(可能原来的文件是客户端的,借鞋会对内存进行操作造成,具体情情况我也不太清楚),说了半天废话终于到重点了,既然只能进行单线程处理,那碰到这种情况该怎么办的,那就利用多进程,对多进程比较好的实现方式就是创建windows系统服务。
关于创建windows服务这篇文章讲解的比较详细,包括对服务的附加到进程中调试也进行了介绍
C#创建windows服务
这是服务中的代码
protected override void OnStart(string[] args)
{
// TODO: 在此处添加代码以启动服务。
Thread.Sleep(1000);
try
{
File.AppendAllText("C:\\" + this.ServiceName + "Log.txt", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff") + " " + this.ServiceName + "服务启动\r\n");
/* EXPORT_IMPORT.fexport2xml就是动态库解析文件的方法
* 另外,这个动态库是32位的 所以在安装时服务页需要使用x86环境编译
* 安装服务的installutil也需要使用 C:\Windows\Microsoft.NET\Framework\v4.0.30319\installutil.exe
* 64位的是C:\Windows\Microsoft.NET\Framework64\v4.0.30319\installutil.exe
*/
int i = EXPORT_IMPORT.fexport2xml(args[0], args[1], Convert.ToInt32(args[2]), Convert.ToInt32(args[3]), args[4], args[5]);
File.Delete("C:\\" + this.ServiceName + ".txt");
File.AppendAllText("C:\\" + this.ServiceName + ".txt", i.ToString());
}
catch (Exception e)
{
File.Delete("C:\\" + this.ServiceName + ".txt");
File.AppendAllText("C:\\" + this.ServiceName + ".txt", "999");
File.AppendAllText("C:\\" + this.ServiceName + "Exception.txt", DateTime.Now.ToString() + " 发生异常:\r\n" + e.StackTrace + "\r\n");
}
finally
{
this.Stop();
}
}
protected override void OnStop()
{
// TODO: 在此处添加代码以执行停止服务所需的关闭操作。
File.AppendAllText("C:\\" + this.ServiceName + "Log.txt", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff") + " " + this.ServiceName + "服务停止\r\n");
}
创建几个静态变量
//服务池
public static ServiceController[] serviceController = new ServiceController[5];
//处理的队列 Key:请求的表示,Value:需要对服务传递的参数
public static Queue<KeyValuePair<Guid,string[]>> myQueue = new Queue<KeyValuePair<Guid, string[]>>();
//信号量标记
public static SemaphoreSlim slim = new SemaphoreSlim(5);
//处理完的结果存放的dictionary
public static Dictionary<Guid, string> dic = new Dictionary<Guid, string>();
//服务池中暂时空闲的服务列表
public static List<ServiceController> FreeserviceController = new List<ServiceController>();
/// <summary>
/// 初始化serviceController 和 FreeserviceController
/// </summary>
private void init()
{
serviceController[0] = new ServiceController("JxXml1");
serviceController[1] = new ServiceController("JxXml2");
serviceController[2] = new ServiceController("JxXml3");
serviceController[3] = new ServiceController("JxXml4");
serviceController[4] = new ServiceController("JxXml5");
FreeserviceController.Add(serviceController[0]);
FreeserviceController.Add(serviceController[1]);
FreeserviceController.Add(serviceController[2]);
FreeserviceController.Add(serviceController[3]);
FreeserviceController.Add(serviceController[4]);
}
数据排队控制以及取得结果的方法
/// <summary>
/// 在服务池空闲时 对需要处理的队列数据进行处理
/// </summary>
public static void doJxXml()
{
//信号量-1
WebApiApplication.slim.WaitAsync();
//取待处理队列的第一个数据
KeyValuePair<Guid, string[]> kp = WebApiApplication.myQueue.Dequeue();
if (kp.Value.Length == 6)
{
//获取空闲服务
ServiceController sc = getFreeService();
sc.MachineName = "localhost";
if (sc != null)
{
sc.Start(kp.Value);
//等待服务执行完毕
sc.WaitForStatus(ServiceControllerStatus.Stopped);
}
//获取服务处理后的结果
string text = File.ReadAllText("C:\\" + sc.ServiceName + ".txt");
//将一个处理结果加入结果对应的dictionary中
WebApiApplication.dic.Add(kp.Key, text);
if (sc!=null)
{
//将停止的服务加入到空闲服务列表中
WebApiApplication.FreeserviceController.Add(sc);
}
}
///施放一个信号量
WebApiApplication.slim.Release();
}
/// <summary>
/// 获取当前服务池中第一个空闲的服务
/// </summary>
/// <returns></returns>
public static ServiceController getFreeService()
{
foreach (ServiceController sc in WebApiApplication.FreeserviceController)
{
if (sc.Status.Equals(ServiceControllerStatus.Stopped))
{
WebApiApplication.FreeserviceController.Remove(sc);
return sc;
}
}
return null;
}
/// <summary>
/// 在结果dictionary中取相应的结果
/// </summary>
/// <param name="guid"></param>
/// <returns></returns>
public static int getResult(Guid guid)
{
int result = 0;
while (true)
{
Thread.Sleep(500);
if (WebApiApplication.dic.ContainsKey(guid))
{
result = Convert.ToInt32(WebApiApplication.dic[guid]);
WebApiApplication.dic.Remove(guid);
break;
}
}
return result;
}
部署到IIS后一定要记得修改IIS程序池标识的权限
这样一番折腾下来,web程序可以对过多的解析文件进行排队,处理,保证5个服务同时运行时其他文件在外等候,如果用户量越来越大而且处理的工程文件普遍较大会造成很长度等待时,这时就需要对服务池进行扩充了,所以一些必要的数据记录还是很有必要的,这样就可以及时了解硬件方面的需求。 有人说启动服务不是也需要时间吗,为什么不把程序一直启动着,,然后需要处理再发命令?
- 那些启动时间慢的服务都是在启动时做了很多操作,如果没任何操作,服务启动后就关闭只需要0.03秒左右;
- C#有现成的OnStart(string[] args)方法可以调用而且可以用来传参,这样就不需要从其他地方取参数,很方便
- 嘻嘻 对进程之间发消息不是很了解,正在了解。