Почему Python для.NET удерживает глобальную блокировку интерпретатора во время конструктора, но не вызывает метод?

У меня есть несколько классов, написанных на C#, которые я хочу использовать из приложения, написанного на Python с использованием Python для.NET.

Ранее, когда я делал что-то подобное, используя Python4Delphi и Python C API, я знаю, насколько важно сохранять глобальную блокировку интерпретатора (GIL) для всего, что взаимодействует с Python, и одновременно освобождать его во время длительных операций, чтобы могли работать другие потоки Python.

Для приобретения GIL, Python для.NET поставляется с удобным Py.GIL() утилита и я написал NoGil чтобы было так же легко выпустить GIL.

Я предполагал, что всякий раз, когда код Python вызывает код C#, GIL удерживается на протяжении всего вызова. Кажется, однако, что GIL не удерживается во время вызовов метода, в то время как он удерживается во время вызова конструктора, как показано в примере ниже.

Вот класс C# и мой NoGil служебный класс с некоторыми регистрациями. Для простоты я не реализовал шаблон Disposable в полном объеме.

using Python.Runtime;
using System;

namespace PythonNet
{
    public class Class1
    {
        public Class1()
        {
            using (new NoGil("constructor"))
            {
                Console.WriteLine("executing constructor");
            }
        }

        public void Method()
        {
            using (new NoGil("method"))
            {
                Console.WriteLine("executing method");
            }
        }
    }

    public class NoGil: IDisposable
    {
        private string _message;
        private IntPtr _state = IntPtr.Zero;

        public NoGil(string message)
        {
            _message = message;
            Console.WriteLine("Before calling BeginAllowThreads from " + message);
            _state = PythonEngine.BeginAllowThreads();
            Console.WriteLine("After calling BeginAllowThreads from " + _message);
        }

        public void Dispose()
        {
            if (_state == IntPtr.Zero)
            {
                Console.WriteLine("B_state == IntPtr.Zero in " + _message);
            }
            else
            {
                Console.WriteLine("Before calling EndAllowThreads from " + _message);
                PythonEngine.EndAllowThreads(_state);
                Console.WriteLine("After calling EndAllowThreads from " + _message);
            }
        }
    }
}

Он используется из Python (после установки пакета pythonnet через pip) следующим образом:

import clr
clr.AddReference("PythonNet.dll")
from PythonNet import Class1
c = Class1()
c.Method()

Выход

Before calling BeginAllowThreads from constructor
After calling BeginAllowThreads from constructor
executing constructor
Before calling EndAllowThreads from constructor
After calling EndAllowThreads from constructor
Before calling BeginAllowThreads from method
Fatal Python error: PyEval_SaveThread: NULL tstate

Current thread 0x0000124c (most recent call first):
  File "test.py", line 5 in <module>

Я попробовал это с Python 2.7 и 3.6 (оба 64-битных), последней версией 2.3.0 Python для.NET и.NET 4.0 и 4.6.1 в качестве моей целевой платформы.

Вопросы:

  1. Это ожидаемое поведение в Python для.NET или я должен сообщить об ошибке?
  2. Если это ожидается, в каких именно ситуациях я могу предположить, что GIL удерживается при вызове кода.NET? Я не нашел никакой документации по этому вопросу.
  3. Или я никогда не должен предполагать что-либо о GIL и всегда получать его при необходимости (т.е. при вызове кода Python через делегатов)? Тогда как мне убедиться, что я не держу его во время длительной не-Python операции?

0 ответов

Другие вопросы по тегам