利用C#守護Python進程的方法

這篇文章主要給大家介紹了關於如何利用C#守護Python進程的相關資料,文中通過示例代碼介紹的非常詳細,對大家學習或者使用C#具有一定的參考學習價值,需要的朋友們下面來一起學習學習吧

背景#

目前我主要負責的一個項目是一個 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 的 消息隊列。所以爲了演示方便,我這裏只列出了和本文相關的核心代碼,在具體的使用過程中,可以依據本文提供的一種實現方法來進行加工處理。

好了,以上就是我在處理客戶端真實IP的方法,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,謝謝大家對神馬文庫的支持。

相關參考#

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章