Micanroules
Micanroules

Reputation: 31

Problem with Python module imports in Unity using Pythonnet

I’m using pythonnet in Unity to integrate Python. Loading Python modules takes some time on the first run in the release version, which is why I used an asynchronous approach with Task and PythonEngine.BeginAllowThreads() to avoid blocking the main Unity thread. While one module imports and works correctly, I’m encountering an issue where one of the libraries inside the module doesn't behave as expected. There are no errors on either the C# side or the Python side. If I switch to sequential loading, everything works as expected.

using Python.Runtime;
using System;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.UI;

namespace UnityPython
{
    public class Test : MonoBehaviour
    {
        [SerializeField] private Button _button;
        private SemaphoreSlim _semaphoreSlim;
        private static dynamic _module;
        private IntPtr? _state;
        private bool _importAsync = false;
        private static string ModuleName = "test_module";

        private void OnEnable()
        {
            _button.onClick.AddListener(TestModulLLM);
            _semaphoreSlim = new SemaphoreSlim(1, 1);
        }

        private void OnDisable()
        {
            _button.onClick.RemoveListener(TestModulLLM);
            _semaphoreSlim.Dispose();
           
        }

        private void OnApplicationQuit()
        {
             if (_state.HasValue)
            {
                PythonEngine.EndAllowThreads(_state.Value);
                Debug.Log("Release the GIL for threads.");
            }
        }

        async void Start()
        {
            Debug.Log("Start Scene");
            _state = PythonEngine.BeginAllowThreads();
            Debug.Log("Initialize the threads' GIL");
            if (_importAsync)
            {
                _module = await LoadAsync();
            }
            else
            {
                _module = Load();
            }
        }

        public async void TestModulLLM()
        {
            try
            {
                await _semaphoreSlim.WaitAsync();
                string result = await Task.Run(() => LLMModuleGetResult());
                Debug.Log("Result LLM: " + result);
            }
            catch (Exception)
            {
                throw;
            }
            finally
            {
                _semaphoreSlim.Release();
            }
        }

        public string LLMModuleGetResult()
        {
            using (Py.GIL())
            {   
                string result = _module.main();
                return result;

            }
        }

        public static dynamic PythonLoadModule()
        {   
            using (Py.GIL())
            {   
                dynamic module = Py.Import(ModuleName);
                return module;
            }
        }
        public static dynamic Load()
        {   
  
            Debug.Log($"Start the import of the sync module {ModuleName}.");
            dynamic module = PythonLoadModule();
            Debug.Log($"End the import of the sync module {ModuleName}.");
            return module;

        }

        public async Task<dynamic> LoadAsync()
        {   
            dynamic module = await Task.Run(() => {
                Debug.Log($"Start the import of the async module {ModuleName}.");
                dynamic module = PythonLoadModule();
                Debug.Log($"End the import of the async module {ModuleName}.");
                return module;
            });
            return module;
        }
    }
}

Python module - test_module.py

from openai import OpenAi
from langsmith import wrappers
from langsmith import traceable

import logging
import time
import os


client = wrappers.wrap_openai(OpenAI(
    api_key=os.environ["API_KEY"]
))


@traceable
def main():
    response = client.chat.completions.create(
        model="gpt-4o",
        messages=[{"role": "user", "content": "Who are you?"}])
    return response.choices[0].message.content

I’ve tried several approaches to resolve the issue:

I have used BeginAllowThreads() as well as without it, but neither solution solved the problem. I also attempted reloading the modules after importing them, but it didn’t help. Additionally, I’ve tried using a Task instead of a Thread, but the issue persists.

Upvotes: 3

Views: 45

Answers (1)

LOST
LOST

Reputation: 3269

When you call EndAllowThreads you essentially take the GIL back.

If your app needs to run Python in multiple threads, it should call BeginAllowThreads but not call EndAllowThreads until just before exiting.

Upvotes: 0

Related Questions