在通过 pythonnet 从 C# 调用 Py.GIL 之前,如何判断 python 脚本是否已完成

问题描述

我有一个 C# 程序,它在单独的 (C#) 线程上启动一个长时间运行的 python 程序。它通过“await Task.Run(() => cntl.dotest());”来实现。

public void dotest()
        {
            PythonEngine.Initialize();
            using (Py.GIL())
            {
                test = Py.Import("tester");  //ok
                test.TestLoop.loop(); //ok
            }
        }

主线程有一个定时器,它周期性地调用一个函数获取一个python变量:

public int getCntr()
        {        
            using (Py.GIL()) 
            {
                int foo = test.TestLoop.cntr; //ok
                test.TestLoop.stopLoop = true; //ok -- causes python to exit normally
                return foo; //first time through,ok; never gets past "using" on second try
            }        
        }

一旦调用,该函数通过设置python变量“test.TestLoop.stopLoop”来停止python程序。

如果我注释掉“stopLoop=true”,一切都会持续运行。

如果包含该语句,则第一次调用可以正常工作,但是当我第二次调用“getCntr”时,“using (Py.GIL())”语句会挂起 C# 程序(即在 VS 调试器中,线程出现正在执行但不会越过“使用 GIL”语句)。我怀疑这是因为python程序已经正常退出了(通过在python中设置“stopLoop”)。

那么,在执行“using GIL()”语句以确定python程序是否已退出之前,我应该做一个测试吗?我考虑在 Python.Runtime 和 PythonEngine 上测试一些东西,但不知道该怎么做。

我认为问题可能是因为“PythonEngine.Initialize”在第二次调用“getCntr”之前结束的线程上。但是,我无法在 doTest 线程之外初始化引擎(因为在 doTest 中会跳过“使用 Py.GIL”块并且永远不会执行“导入”)。

这个问题可能类似于Python.Net: Do I need "using (Py.GIL())" in a callback from .Net to Python?

我咨询了 *http://pythonnet.github.io/https://github.com/pythonnet/pythonnet/wiki/Threading 但没有找到答案。

这是我的解决方案,有效,但看起来很麻烦:

  1. 添加一个变量(字符串 aLock = "";),用于指示 doTest 线程何时完成。
  2. 当 doTest 线程返回时(即在“await Task.Run()”语句之后),将此变量设置为“done”。
  3. 在 getCntr 中的“使用 GIL”块之前测试这个变量 (aLock !="done")。
  4. 在阅读和设置之前锁定aLock。

解决方法

请参阅https://github.com/pythonnet/pythonnet/blob/16f04e9c0281cd1e4c266399b35ee89069eb36fe/src/embed_tests/TestInterrupt.cs

TL;DR; 您应该在应用主线程中调用 PythonEngine.Initialize() 后跟 PythonEngine.BeginAllowThreads()。这将解决“因为 using Py.GIL 块被跳过”。

实际上,在 PythonEngine.Initialize 之外的 doTest 场景中,您应该看到 Py.GIL 挂在 doTest 中,因为 GIL 由名为 {{ 1}}。

上述原因很简单:当一个线程调用PythonEngine.Initialize时,调用完成时它有GIL锁。我的猜测是它的行为方式是为了方便编写单线程程序。 PythonEngine.Initialize() 显式释放 GIL,以便其他线程可以运行 Python 代码。执行此操作时,主线程也必须使用 BeginAllowThreads

UPD 我刚刚注意到,您链接的线程页面实际上是 mentions that