利用C#守护Python进程的方法
背景#
目前我主要负责的一个项目是一个C/S架构的客户端开发,前端主要是通过WPF相关技术来实现,后端是通过Python来实现,前后端的数据通信则是通过MQ的方式来进行处理。由于Python进程是需要依赖客户端进程来运行,为了保证后端业务进程的稳定性,就需要通过一个守护进程来守护Python进程,防止其由于未知原因而出现进程退出的情况。这里简单记录一下我的一种实现方式。
实现#
对于我们的系统而言,我们的Python进程只允许存在一个,因此,对应的服务类型要采用单例模式,这一部分代码相对简单,就直接贴出来了,示例代码如下所示:
publicpartialclassPythonService { privatestaticreadonlyobject_locker=newobject(); privatestaticPythonService_instance; publicstaticPythonServiceCurrent { get { if(_instance==null) { lock(_locker) { if(_instance==null) { _instance=newPythonService(); } } } return_instance; } } privatePythonService() { } }
创建独立进程#
由于后端的Python代码运行需要安装一些第三方的扩展库,所以为了方便,我们采用的方式是总结将python安装文件及扩展包和他们的代码一并打包到我们的项目目录中,然后创建一个Python进程,在该进程中通过设置环境变量的方式来为Python进程进行一些环境配置。示例代码如下所示:
publicpartialclassPythonService { privatestring_workPath=>Path.Combine(AppDomain.CurrentDomain.BaseDirectory,"scripts"); privatestring_pythonPath=>Path.Combine(_workPath,"python27"); privateboolisRunning=false; privateinttaskPID=-1; publicvoidStart() { taskPID=CreateProcess(); isRunning=taskPID!=-1; varmsg=isRunning?"服务启动成功...":"服务启动失败..."; Trace.WriteLine(msg); } publicvoidStop() { KillProcessAndChildren(taskPID); isRunning=false; taskPID=-1; } privateintCreateProcess() { KillProcessAndChildren(taskPID); intpid=-1; varpsi=newProcessStartInfo(Path.Combine(_pythonPath,"python.exe")) { UseShellExecute=false, WorkingDirectory=_workPath, ErrorDialog=false }; psi.CreateNoWindow=true; varpath=psi.EnvironmentVariables["PATH"]; if(path!=null) { vararray=path.Split(new[]{';'}).Where(p=>!p.ToLower().Contains("python")).ToList(); array.AddRange(new[]{_pythonPath,Path.Combine(_pythonPath,"Scripts"),_workPath}); psi.EnvironmentVariables["PATH"]=string.Join(";",array); } varps=newProcess{StartInfo=psi}; if(ps.Start()) { pid=ps.Id; } returnpid; } privatestaticvoidKillProcessAndChildren(intpid) { //Cannotclose'systemidleprocess'. if(pid<=0) { return; } ManagementObjectSearchersearcher=newManagementObjectSearcher("Select*FromWin32_ProcessWhereParentProcessID="+pid); ManagementObjectCollectionmoc=searcher.Get(); foreach(ManagementObjectmoinmoc) { KillProcessAndChildren(Convert.ToInt32(mo["ProcessID"])); } try { Processproc=Process.GetProcessById(pid); proc.Kill(); } catch(ArgumentException) { //Processalreadyexited. } catch(Win32Exception) { //Accessdenied } } }
这里有一点需要注意一下,建议使用PID来标识我们的Python进程,因为如果你使用进程实例或其它方式来对当前运行的进程设置一个引用,当该进程出现一些未知退出,这个时候你通过哪个引用来进行相关操作是会出问题的。
创建守护进程#
上面我们的通过记录当前正在运行的进程的PID来标识我们的进程,那对应守护进程,我们就可以通过进程列表查询的方式来进行创建,在轮询的过程中,如果未找到对应PID的进程则表明该进程已经退出,需要重新创建该进程,否则就不执行任何操作,示例代码如下所示:
publicpartialclassPythonService { privateCancellationTokenSourcects; privatevoidStartWatch(CancellationTokentoken) { Task.Factory.StartNew(()=> { while(!token.IsCancellationRequested) { varhas=Process.GetProcesses().Any(p=>p.Id==taskPID); Trace.WriteLine($"MQ状态:{DateTime.Now}-{has}"); if(!has) { taskPID=CreateProcess(_reqhost,_subhost,_debug); isRunning=taskPID>0; varmsg=isRunning?"MQ重启成功":"MQ重启失败,等待下次重启"; Trace.WriteLine($"MQ状态:{DateTime.Now}-{msg}"); } Thread.Sleep(2000); } },token); } }
这里我使用的是Thread.Sleep(2000)方式来继续线程等待,你也可以使用awaitTask.Delay(2000,token),但是使用这种方式在发送取消请求时会产生一个TaskCanceledException的异常。所以为了不产生不必要的异常信息,我采用第一种解决方案。
接着,完善我们的Start和Stop方法,示例代码如下所示:
publicvoidStart() { taskPID=CreateProcess(); isRunning=taskPID!=-1; if(isRunning) { cts=newCancellationTokenSource(); StartWatch(cts.Token); } varmsg=isRunning?"服务启动成功...":"服务启动失败..."; Trace.WriteLine(msg); } publicvoidStop() { cts?.Cancel(false); cts?.Dispose(); KillProcessAndChildren(taskPID); taskPID=-1; isRunning=false; }
最后,上层调用就相对简单一下,直接调用Start方法和Stop方法即可。
总结#
在我们的实际项目代码中,PythonService的代码要比上面的代码稍微复杂一些,我们内部还添加了一个MQ的消息队列。所以为了演示方便,我这里只列出了和本文相关的核心代码,在具体的使用过程中,可以依据本文提供的一种实现方法来进行加工处理。
好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对毛票票的支持。
相关参考#
- Killaone-filePythonprocessinC#
- 用c#实现通用守护进程