3

I'm using Pythonnet to embed a Python script launcher into a C# WPF application. I can pass variable to python scripts using Scope and i get the result on a console using MVVM pattern.

Now I want to allow the user to stop a script execution at anytime. I couldn't find how to make that work in order to close the Thread properly.

class PythonRuntime
{
    private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();

    private MainViewModel viewModel;
    private string pythonCode;

    private bool runtimeThreadLock = false;
    Thread thread;

    private PyScope scope;
    private dynamic pyThread;
    private dynamic pyLock;

    ConsoleWriter consoleWriter;


    public PythonRuntime(MainViewModel viewModel, ConsoleWriter consoleWriter)
    {
        this.viewModel = viewModel;
        this.consoleWriter = consoleWriter;
        SetUpPython();    
    }

    public string PythonCode { get => pythonCode; set => pythonCode = value; }

    private void SetUpPython()
    {
        PythonEngine.Initialize(true);

        scope = Py.CreateScope();
        // consoleWriter to make python prints into C# UI
        scope.Set("Console", consoleWriter);
    }

    public void LaunchScript()
    {
        if (!runtimeThreadLock)
        {
            thread = new Thread(PythonNetTest);                
            thread.Start();
        }
    }

    public void StopScript()
    {
        // ???
    }

    [HandleProcessCorruptedStateExceptions]
    private void PythonNetTest()
    {
        runtimeThreadLock = true;
        pyThread = PythonEngine.BeginAllowThreads();
        pyLock = PythonEngine.AcquireLock();

        using (Py.GIL())
        {
            try
            {
                scope.Exec(pythonCode);
            }
            catch (PythonException exception)
            {
                consoleWriter.WriteError(exception.ToString());
            }
        }

        PythonEngine.ReleaseLock(pyLock);
        PythonEngine.EndAllowThreads(pyThread);
        runtimeThreadLock = false;
    }
}  

Besides my question, I was wondering what is the purpose of wrapping code in using(Py.GIL()). Because with or whithout it my script runs the same way.

  • Pythonnet : 2.4.0
  • Python : 2.7.2 32bit
  • NetFramework : 4.7.1
Dairon
  • 159
  • 1
  • 14

1 Answers1

3

OK, I'm just beginning work on embedding CPython and may know only a little more than you. What that caveat...

First, you need to get your script to terminate. When it does the call to .Exec() will return, and the thread will exit. If your script runs for a finite amount of time then you just wait for it. Otherwise, you must arrange some signal that it should exit.

Second, mainline will wait for thread to complete using one of several .NET patterns described at: How to wait for thread to finish with .NET?

using(Py.GIL()) is shorthand for PythonEngine.AcquireLock(); and PythonEngine.ReleaseLock(pyLock); It creates an IDisposable object that acquires the lock and then releases it on Dispose(). So, in your sample, it is redundant.

I'm unsure effects of your call to BeginAllowThreads(). Documentation says that it releases the lock to allow other threads. When you call it you don't have the GIL. Next line acquires the GIL. Therefore, it appears to have no function to me.

See https://docs.python.org/3/c-api/init.html for details on threading. This seems more related to python threading and saving thread state so that other non-python things can be done. This is python 3. Python 2 did not seem to support the equivalent.

TomU
  • 401
  • 3
  • 9
  • Thanks for the explanation about using(Py.GIL()). But how can you send a signal from the C# to the running python script in order to make it stop ? – Dairon May 21 '18 at 12:02
  • That all depends on what the thread is doing. If it reads messages from a queue then design a "quit" message. If it loops repetitively then set a "PleaseQuitNow" variable to true (and manipulated in thread safe way). I might design the python code to be a method called repeatedly from a loop in C# so I can use the .NET threading primitives easily. Or, you can write python code quit when a specific file has been created. – TomU May 22 '18 at 18:22
  • This is still a question to me. I just managed to use Thread.Abort() to stop the C# thread that executes `scope.Exec(myScript1);`. Later when I recreate the thread and call `scope.Exec(myScript2);` again, the previous code in myScript1 continues execution together with myScript2. In python console I can hit Ctrl + C, stopping the code and gettting "KeyboardInterrupt". Isn't there a way to make some similar command? – A. Vieira Sep 30 '21 at 18:35