用 C# 来守护 Python 进程
背景
目前我主要负责的一个项目是一个 C/S 架构的客户端开发,前端主要是通过 WPF
相关技术来实现,后端是通过 Python
来实现,前后端的数据通信则是通过 MQ
的方式来进行处理。由于 Python
进程是需要依赖客户端进程来运行,为了保证后端业务进程的稳定性,就需要通过一个 守护进程
来守护 Python 进程,防止其由于未知原因而出现进程退出的情况。这里简单记录一下我的一种实现方式。
实现
对于我们的系统而言,我们的 Python 进程只允许存在一个,因此,对应的服务类型要采用单例模式,这一部分代码相对简单,就直接贴出来了,示例代码如下所示:
public partial class PythonService
{
private static readonly object _locker = new object();
private static PythonService _instance;
public static PythonService Current
{
get
{
if (_instance == null)
{
lock (_locker)
{
if (_instance == null)
{
_instance = new PythonService();
}
}
}
return _instance;
}
}
private PythonService()
{
}
}
创建独立进程
由于后端的 Python 代码运行需要安装一些第三方的扩展库,所以为了方便,我们采用的方式是总结将 python 安装文件及扩展包和他们的代码一并打包到我们的项目目录中,然后创建一个 Python 进程,在该进程中通过设置环境变量的方式来为 Python 进程进行一些环境配置。示例代码如下所示:
public partial class PythonService
{
private string _workPath => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "scripts");
private string _pythonPath => Path.Combine(_workPath, "python27");
private bool isRunning = false;
private int taskPID = -1;
public void Start()
{
taskPID = CreateProcess();
isRunning = taskPID != -1;
var msg = isRunning ? "服务启动成功..." : "服务启动失败...";
Trace.WriteLine(msg);
}
public void Stop()
{
KillProcessAndChildren(taskPID);
isRunning = false;
taskPID = -1;
}
private int CreateProcess()
{
KillProcessAndChildren(taskPID);
int pid = -1;
var psi = new ProcessStartInfo(Path.Combine(_pythonPath, "python.exe"))
{
UseShellExecute = false,
WorkingDirectory = _workPath,
ErrorDialog = false
};
psi.CreateNoWindow = true;
var path = psi.EnvironmentVariables["PATH"];
if (path != null)
{
var array = 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);
}
var ps = new Process { StartInfo = psi };
if (ps.Start())
{
pid = ps.Id;
}
return pid;
}
private static void KillProcessAndChildren(int pid)
{
// Cannot close 'system idle process'.
if (pid <= 0)
{
return;
}
ManagementObjectSearcher searcher = new ManagementObjectSearcher("Select * From Win32_Process Where ParentProcessID=" + pid);
ManagementObjectCollection moc = searcher.Get();
foreach (ManagementObject mo in moc)
{
KillProcessAndChildren(Convert.ToInt32(mo["ProcessID"]));
}
try
{
Process proc = Process.GetProcessById(pid);
proc.Kill();
}
catch (ArgumentException)
{
// Process already exited.
}
catch (Win32Exception)
{
// Access denied
}
}
}
这里有一点需要注意一下,建议使用 PID 来标识我们的 Python 进程,因为如果你使用进程实例或其它方式来对当前运行的进程设置一个引用,当该进程出现一些未知退出,这个时候你通过哪个引用来进行相关操作是会出问题的。
创建守护进程
上面我们的通过记录当前正在运行的进程的 PID 来标识我们的进程,那对应守护进程,我们就可以通过进程列表查询的方式来进行创建,在轮询的过程中,如果未找到对应 PID 的进程则表明该进程已经退出,需要重新创建该进程,否则就不执行任何操作,示例代码如下所示:
public partial class PythonService
{
private CancellationTokenSource cts;
private void StartWatch(CancellationToken token)
{
Task.Factory.StartNew(() =>
{
while (!token.IsCancellationRequested)
{
var has = Process.GetProcesses().Any(p => p.Id == taskPID);
Trace.WriteLine($"MQ状态:{DateTime.Now}-{has}");
if (!has)
{
taskPID = CreateProcess(_reqhost, _subhost, _debug);
isRunning = taskPID > 0;
var msg = isRunning ? "MQ重启成功" : "MQ重启失败,等待下次重启";
Trace.WriteLine($"MQ状态:{DateTime.Now}-{msg}");
}
Thread.Sleep(2000);
}
}, token);
}
}
这里我使用的是
Thread.Sleep(2000)
方式来继续线程等待,你也可以使用await Task.Delay(2000,token)
,但是使用这种方式在发送取消请求时会产生一个TaskCanceledException
的异常。所以为了不产生不必要的异常信息,我采用第一种解决方案。
接着,完善我们的 Start
和 Stop
方法,示例代码如下所示:
public void Start()
{
taskPID = CreateProcess();
isRunning = taskPID != -1;
if (isRunning)
{
cts = new CancellationTokenSource();
StartWatch(cts.Token);
}
var msg = isRunning ? "服务启动成功..." : "服务启动失败...";
Trace.WriteLine(msg);
}
public void Stop()
{
cts?.Cancel(false);
cts?.Dispose();
KillProcessAndChildren(taskPID);
taskPID = -1;
isRunning = false;
}
最后,上层调用就相对简单一下,直接调用 Start
方法和 Stop
方法即可。
总结
在我们的实际项目代码中,PythonService
的代码要比上面的代码稍微复杂一些,我们内部还添加了一个 MQ 的 消息队列。所以为了演示方便,我这里只列出了和本文相关的核心代码,在具体的使用过程中,可以依据本文提供的一种实现方法来进行加工处理。
相关参考
补充
这篇文章很荣幸能被 张队 转载到他的公众号上面让更多的技术爱好者看到了。我看到文章的评论区里有朋友说了为什么不用 pythonnet
这种第三方集成框架以及为什么需要守护进程,这里我对这两个问题解答一下
- 为什么不使用
pythonnet
这种第三方的成熟框架?
这里我需要说明一下,我们的客户端对应的后端服务是
python
写的,并且脚本数量巨多无比,每个脚本之间又是相互独立的模块,相关的依赖库都不一样,所以这就导致一个问题,如果使pythonnet
或ironpython
这种集成框架,那么每个模块需要使用到的依赖包就需要放到我们客户端来维护管理安装。从工程设计的角度来讲,这个工作由我们客户端组来做是不太合适的,虽然技术上是可行的,但是这无疑是一个坑。
- 为什么需要一个守护进程?
因为我们的
python
后端服务调用的很多第三方组件(部分是非自研)是多种类型的,后端服务无法保证能稳定调用每一个第三方组件不崩溃,这就要求我们客户端必须要做一个守护进程来监测后端服务进程的状态,当其崩溃后要能重新启动。
我很能理解为什么有很多朋友会有上面两个疑惑,其实做技术的很多都会陷入一个误区:单纯的考虑技术实现,而不关注业务解耦。这个怎么说呢,有好处也有不好的地方,但是我个人觉得,如果只是关注技术,而不切入业务,最后即使每个技术细节实现的很完美,但是业务紧耦合,这个项目依旧难以 可持续发展。
用 C# 来守护 Python 进程的更多相关文章
- 使用Supervisor守护Python进程
1.需求 现在有一个进程需要每时每刻不断的跑,但是这个进程又有可能由于各种原因有可能中断.当进程中断的时候我希望能自动重新启动它,此时,就需要使用到了Supervisor.Supervisor起到守护 ...
- python进程、线程、协程(转载)
python 线程与进程简介 进程与线程的历史 我们都知道计算机是由硬件和软件组成的.硬件中的CPU是计算机的核心,它承担计算机的所有任务. 操作系统是运行在硬件之上的软件,是计算机的管理者,它负责资 ...
- Python进程、线程、协程详解
进程与线程的历史 我们都知道计算机是由硬件和软件组成的.硬件中的CPU是计算机的核心,它承担计算机的所有任务. 操作系统是运行在硬件之上的软件,是计算机的管理者,它负责资源的管理和分配.任务的调度. ...
- python/进程线程的总结
python/进程线程的总结 一.进程和线程的描述: 进程:最小的资源管理单位 线程:最小的执行单位 执行一个进程时就默认执行一个线程(主线程) 进程和线程的工作方式: 串行: 假如共有A.B.C任务 ...
- python2.0 s12 day8 _ python线程&python进程
1.进程.与线程区别2.cpu运行原理3.python GIL全局解释器锁4.线程 1.语法 2.join 3.线程锁之Lock\Rlock\信号量 4.将线程变为守护进程 5.Event事件 6.q ...
- python 进程和线程(代码知识部分)
二.代码知识部分 一 multiprocessing模块介绍: python中的多线程无法利用多核优势,如果想要充分地使用多核CPU的资源(os.cpu_count()查看),在python中大部分情 ...
- Supervisor4.0和python2.7的crit问题,导致python进程阻塞
1.问题原因 Supervisor高版本在守护python2.7的服务时,会crit并报错并倒至进程阻塞(python进程存在,但不在运行)的问题,一般会和字符集有关系 <type 'excep ...
- [ python ] 进程的操作
目录 (见右侧目录栏导航)- 1. 前言- 2. multiprocess模块- 2.1 multiprocess.Process模块 - 2.2 使用Process模块创建进程 - 2. ...
- python进程概要
进程 狭义:正在运行的程序实例. 广义:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动,他是操作系统动态执行的基本单元. python的进程都是并行的. 并行:两个进程同时执行一起走. ...
随机推荐
- 自己搭建传统ocr识别项目学习
大批生成文集训练集: https://www.cnblogs.com/skyfsm/p/8436820.html 基于深度学习的文字识别(3755个汉字) http://www.cnblogs.com ...
- POJ 1661 暴力dp
题意略. 思路: 很有意思的一个题,我采用的是主动更新未知点的方式,也即刷表法来dp. 我们可以把整个路径划分成横向移动和纵向移动,题目一开始就给出了Jimmy的高度,这就是纵向移动的距离. 我们dp ...
- FZU - 1914
题意略. 思路: 我们应该着重关注负数对当前数列的影响,由于前缀和的性质,我们都是从当前数字向前加,这其实也是在枚举以哪个下标作为开头. 详见代码: #include<stdio.h> # ...
- php中++ --运算
$x = 5; echo $x; echo "---"; // echo ++$x + $x; //6+6=12 $x=6 // echo ++$x + 5; //6+5=12 $ ...
- session一致性的解决方案
更多内容,欢迎关注微信公众号:全菜工程师小辉.公众号回复关键词,领取免费学习资料. 什么是session? 服务器为每个用户创建一个会话,存储用户的相关信息,以便多次请求能够定位到同一个上下文,这个相 ...
- ccflow关于流程引擎的父子流程的基本概念
关键字:驰骋BPM jflow,ccflow, 驰骋工作流引擎 父子流程概念:在管理活动中,通用的部分,公用的部分,可以被重复执行的部分的流程管理活动我们把他独立出来,成为子流程,可以被其他流程的管理 ...
- Python 基础(五)
pandas缺失值处理 import pandas as pd importrandom df01 = pd.DataFrame(np.random.randint(1,9),size = (4,4) ...
- CF1027D Mouse Hunt 思维
Mouse Hunt time limit per test 2 seconds memory limit per test 256 megabytes input standard input ou ...
- codeforces 454 E. Little Pony and Summer Sun Celebration(构造+思维)
题目链接:http://codeforces.com/contest/454/problem/E 题意:给出n个点和m条边,要求每一个点要走指定的奇数次或者是偶数次. 构造出一种走法. 题解:可能一开 ...
- CSU 1803 2016 湖南省2016省赛
1803: 2016 Submit Page Summary Time Limit: 5 Sec Memory Limit: 128 Mb Submitted: 1416 ...