Reputation: 158
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.
Upvotes: 3
Views: 2465
Reputation: 401
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.
Upvotes: 3