SoFunction
Updated on 2025-03-01

Methods to use C# to protect Python processes

background#

At present, one of the projects I am mainly responsible for is client development of a C/S architecture. The front-end is mainly implemented through WPF-related technologies, the back-end is implemented through Python, and the data communication between the front-end and back-end is processed through MQ. Since Python processes need to rely on client processes to run, in order to ensure the stability of the back-end business process, it is necessary to use a daemon to protect the Python processes to prevent the process from exiting due to unknown reasons. Here I will briefly record one of my implementation methods.

accomplish#

For our system, our Python process only allows one, so the corresponding service type must adopt the singleton mode. This part of the code is relatively simple, so it is posted directly. The sample code is as follows:

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()
 {

 }
}

Create a standalone process#

Since the backend Python code needs to be run to install some third-party extension libraries, for convenience, the method we adopt is to summarize the python installation files, extension packages and their code into our project directory, and then create a Python process, in which the Python process is configured with some environments by setting environment variables. The sample code is as follows:

public partial class PythonService
{
 private string _workPath => (, "scripts");
 private string _pythonPath => (_workPath, "python27");

 private bool isRunning = false;
 private int taskPID = -1;

 public void Start()
 {
 taskPID = CreateProcess();
 isRunning = taskPID != -1;

 var msg = isRunning ? "Service started successfully..." : "Service startup failed...";
 (msg);
 }

 public void Stop()
 {
 KillProcessAndChildren(taskPID);

 isRunning = false;
 taskPID = -1;
 }

 private int CreateProcess()
 {
 KillProcessAndChildren(taskPID);

 int pid = -1;
 var psi = new ProcessStartInfo((_pythonPath, ""))
 {
 UseShellExecute = false,
 WorkingDirectory = _workPath,
 ErrorDialog = false
 };

  = true;

 var path = ["PATH"];
 if (path != null)
 {
 var array = (new[] { ';' }).Where(p => !().Contains("python")).ToList();
 (new[] { _pythonPath, (_pythonPath, "Scripts"), _workPath });
 ["PATH"] = (";", array);
 }
 var ps = new Process { StartInfo = psi };
 if (())
 {
 pid = ;
 }
 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 = ();
 foreach (ManagementObject mo in moc)
 {
 KillProcessAndChildren(Convert.ToInt32(mo["ProcessID"]));
 }
 try
 {
 Process proc = (pid);
 ();
 }
 catch (ArgumentException)
 {
 // Process already exited.
 }
 catch (Win32Exception)
 {
 // Access denied
 }
 }
}

There is one thing to note here. It is recommended to use PID to identify our Python process, because if you use process instances or other methods to set a reference to the currently running process, when the process has some unknown exit, there will be a problem with which reference you use to perform related operations.

Create a daemon#

Our above identifies our process by recording the PID of the currently running process. The corresponding daemon process can be created through process list query. During the polling process, if the process with the corresponding PID is not found, it means that the process has exited and the process needs to be recreated, otherwise no operation will be performed. The sample code is as follows:

public partial class PythonService
{
 private CancellationTokenSource cts;

 private void StartWatch(CancellationToken token)
 {
 (() =>
 {
  while (!)
  {
  var has = ().Any(p =>  == taskPID);
  ($"MQstate:{}-{has}");
  if (!has)
  {
   taskPID = CreateProcess(_reqhost, _subhost, _debug);
   isRunning = taskPID > 0;

   var msg = isRunning ? "MQ restart successfully" : "MQ restart failed, wait for the next restart";
   ($"MQstate:{}-{msg}");
  }

  (2000);
  }
 }, token);
 }
}

Here I am using the (2000) method to continue thread waiting. You can also use await (2000,token), but using this method will generate a TaskCanceledException when sending a cancel request. So in order not to generate unnecessary exception information, I adopt the first solution.

Next, perfect our Start and Stop methods, the sample code is as follows:

public void Start()
{
 taskPID = CreateProcess();
 isRunning = taskPID != -1;

 if (isRunning)
 {
 cts = new CancellationTokenSource();
 StartWatch();
 }

 var msg = isRunning ? "Service started successfully..." : "Service startup failed...";
 (msg);
}

public void Stop()
{
 cts?.Cancel(false);
 cts?.Dispose();

 KillProcessAndChildren(taskPID);
 taskPID = -1;

 isRunning = false;
}

Finally, the upper-level call is relatively simple, just call the Start method and the Stop method directly.

Summarize#

In our actual project code, the PythonService code is slightly more complicated than the above code, and we also added an MQ message queue internally. So for the sake of demonstration convenience, I only listed the core code related to this article. In the specific use process, it can be processed according to an implementation method provided in this article.

OK, the above is the entire content of this article. I hope that the content of this article has certain reference value for your study or work. Thank you for your support.

Related reference#

  • Kill a one-file Python process in C#
  • Implementing a general daemon with C#