Background

Currently a project is a C / S architecture of client development, mainly through the front-end WPFto achieve the related technology, the back-end through Pythonto implementation, data communication is through the front and rear ends MQto be treated the way. Since the Python process is the need to rely on a client process to run, in order to ensure the stability of the back-end business processes, you need a demon to guard Python process, the process exits to prevent their situation occurs for unknown reasons. Here is a simple record of one of my implementations.

Implementation

For our system, only one of our Python processes is allowed. Therefore, the corresponding service type should use the singleton mode. This part of the code is relatively simple and is directly posted. The sample code is as follows:

Copypublic 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 an independent process

Since the backend Python code needs to install some third-party extension libraries, for convenience, the way we use it is to summarize the python installation files and extensions and their code into our project directory, and then create a Python. A process in which some environment is configured for a Python process by setting environment variables. The sample code is as follows:

Copypublic 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 ? "Service success..." : "Service fail...";
        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
        }
    }
}

One thing to note here is that it is recommended to use PID to identify our Python process, because if you use a process instance or other way to set a reference to the currently running process, when the process has some unknown exit, which reference do you pass? There will be problems with the related operations.

Create daemon

Above we identify our process by recording the PID of the currently running process, then the corresponding daemon, we can create by process list query, in the process of polling, if the process corresponding to PID is not found This indicates that the process has exited and needs to be recreated, otherwise no action is taken. The sample code is as follows:

Copypublic 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 Status:{DateTime.Now}-{has}");
                if (!has)
                {
                    taskPID = CreateProcess(_reqhost, _subhost, _debug);
                    isRunning = taskPID > 0;

                    var msg = isRunning ? "MQ Restart OK" : "MQ restart failed";
                    Trace.WriteLine($"MQ Status:{DateTime.Now}-{msg}");
                }

                Thread.Sleep(2000);
            }
        }, token);
    }
}

I’m using here is Thread.Sleep(2000)the way to continue to wait for a thread, you can also use await Task.Delay(2000,token), but the use of this approach will generate when sending a cancellation request TaskCanceledExceptionexception. So in order not to generate unnecessary exception information, I used the first solution.

Subsequently, improve our Startand Stopmethods, sample code is as follows:

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

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

    var msg = isRunning ? "Service success..." : "Service failed...";
    Trace.WriteLine(msg);
}

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

    KillProcessAndChildren(taskPID);
    taskPID = -1;

    isRunning = false;
}

Finally, Über relatively simple look directly call the Startmethod and the Stopmethod can be.

Summary
#

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

Related reference
#

Orignal link:https://www.cnblogs.com/hippieZhou/p/11504552.html