Как очистить перезапуск PythonNet при последовательном запуске сценариев Python на C#?C#

Место общения программистов C#
Ответить Пред. темаСлед. тема
Anonymous
 Как очистить перезапуск PythonNet при последовательном запуске сценариев Python на C#?

Сообщение Anonymous »

Я использую PythonNet в приложении C# для последовательного запуска нескольких сценариев Python, каждый из которых содержится в разных модулях. Однако я сталкиваюсь с непоследовательными ошибками MemoryAccessViolation, которые иногда возникают во время выполнения программы, при завершении работы или даже в случайное время. Эти ошибки не следуют определенному шаблону, и их трудно надежно воспроизвести.
Когда я повторно использую один и тот же экземпляр PythonNet для запуска разных сценариев, возникают ошибки. Попытка завершить работу и перезагрузить экземпляр PythonNet также приводит к этим проблемам доступа к памяти. Кроме того, эти ошибки не перехватываются блоками try-catch, и программа часто аварийно завершает работу с кодом ошибки нарушения прав доступа.
Вот соответствующий фрагмент кода, показывающий, как я инициализирую и удаляю Среда Python:

Код: Выделить всё

    /// 
/// Manages the Python environment using pythonnet.
/// 
public class PythonNetEnv : IDisposable
{
/// 
/// Indicates if the environment is initialized.
/// 
public bool IsInitialized { get; private set; }

private readonly PythonEnv _env;
private PythonEngine? _engine;
private string? _originalPath;
private string? _originalPythonhome;
private CancellationTokenSource _cts = new();

/// 
/// Initializes a new instance of the  class.
/// 
/// The path to the Python environment directory.
public PythonNetEnv(PythonEnv pythonEnv)
{
_env = pythonEnv;
Initialize();
}

/// 
/// Initializes the Python environment.
/// 
private void Initialize()
{
if (IsInitialized) return;

// Construct the necessary paths
string scriptsPath = Path.Combine(_env.EnvPath, "Scripts");
string libraryPath = Path.Combine(_env.EnvPath, "Library");
string binPath = Path.Combine(_env.EnvPath, "bin");
string executablePath = Path.Combine(_env.EnvPath, "Library", "bin");
string mingwBinPath = Path.Combine(_env.EnvPath, "Library", "mingw-w64", "bin");

// Get the current PATH environment variable
_originalPath = Environment.GetEnvironmentVariable("PATH");
_originalPythonhome = Environment.GetEnvironmentVariable("PYTHONHOME");

// Set the new PATH environment variable
string newPath = $"{_env.EnvPath};{scriptsPath};{libraryPath};{binPath};{executablePath};{mingwBinPath};{_originalPath}";
Environment.SetEnvironmentVariable("PATH", newPath, EnvironmentVariableTarget.Process);

// Set the PYTHONHOME environment variable
Environment.SetEnvironmentVariable("PYTHONHOME", _env.EnvPath, EnvironmentVariableTarget.Process);

// Extract the major and minor version numbers from the provided version string
string[] versionParts = _env.Version.Split('.');
if (versionParts.Length < 2)
throw new ArgumentException("Invalid Python version format.  Expected format: main.current.path (e.g., 3.8.20)");

string majorVersion = versionParts[0];
string minorVersion = versionParts[1];

// Construct the Python runtime DLL path based on the version
string pythonDllPath = Path.Combine(_env.EnvPath, $"python{majorVersion}{minorVersion}.dll");

// Explicitly set the Python runtime DLL path
Runtime.PythonDLL = pythonDllPath;

// Set PythonEngine.PythonHome
PythonEngine.PythonHome = _env.EnvPath;
if (!PythonEngine.IsInitialized)
_engine = new PythonEngine();

// Allow Python threads to run independently of the main thread
PythonEngine.BeginAllowThreads();

IsInitialized = true;
}

/// 
/// Runs a Python script from a specific path with specified arguments.
/// 
/// The path to the Python script.
/// The working directory for the script.
/// The arguments to pass to the script.
public async Task RunPythonScript(string scriptPath, string workingDirectory, string arguments)
{
if (!IsInitialized) throw new InvalidOperationException("Python environment is not initialized.");
await Task.Run(() =>
{
string currentDictionary = Environment.CurrentDirectory;
using (Py.GIL())
{
try
{
Environment.CurrentDirectory = workingDirectory;
dynamic sys = Py.Import("sys");
dynamic io = Py.Import("io");

string moduleName = Path.GetFileNameWithoutExtension(scriptPath);
Debug.WriteLine($"Importing module: {moduleName}");

dynamic stdout_capture = io.StringIO();
dynamic stderr_capture = io.StringIO();
sys.stdout = stdout_capture;
sys.stderr = stderr_capture;
_cts = new CancellationTokenSource();
_ = Task.Run(() => CaptureOutput(stdout_capture, _cts.Token));
_ = Task.Run(() => CaptureOutput(stderr_capture, _cts.Token));

try
{
ExecutePythonScript(scriptPath);
}
finally
{
_cts.Cancel();
RestoreStandardOutputs(sys, stdout_capture, stderr_capture);
}
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
Console.WriteLine($"Stack Trace: {ex.StackTrace}");
}
}
Environment.CurrentDirectory = currentDictionary;
});
}

private async Task CaptureOutput(dynamic captureStream, CancellationToken token)
{
string currentOutput = "";
while (!token.IsCancellationRequested)
{
try
{
using (Py.GIL())
{
var currentOutputValue = captureStream.getvalue();
if (currentOutputValue != null)
currentOutput = currentOutputValue.ToString(); // It happens somethimes here, while the code is running.  Throws an error but the try catch does also not prevent exiting of the program.
}
}
catch (System.AccessViolationException ex) { Debug.WriteLine(ex); }
// Some more unrelated code...
}
}

private static void ExecutePythonScript(string scriptPath)
{
using (Py.GIL())
{
using dynamic scope = Py.CreateScope();
scope.Set("__name__", "__main__");
scope.Exec(File.ReadAllText(scriptPath)); // It happens somethimes here, while the code is running. Throws an error but the try catch does also not prevent exiting of the program.
}
}

private static void RestoreStandardOutputs(dynamic sys, dynamic stdout_capture, dynamic stderr_capture)
{
using (Py.GIL())
{
sys.stdout = sys.__stdout__;
sys.stderr = sys.__stderr__;
_ = stdout_capture.getvalue().ToString();
_ = stderr_capture.getvalue().ToString();
}
}

/// 
/// Disposes of the Python environment, shutting down the Python engine.
/// 
public void Dispose()
{
if (!IsInitialized)
return;

try
{
_cts.Dispose();
Debug.WriteLine("Shutdown");

PythonEngine.Shutdown(); // Somewhere here does it happen at most. The catch does not work.
_engine?.Dispose();
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
finally
{
Environment.SetEnvironmentVariable("PATH", _originalPath, EnvironmentVariableTarget.Process);
Environment.SetEnvironmentVariable("PYTHONHOME", _originalPythonhome, EnvironmentVariableTarget.Process);

}
}
}
Дополнительная информация:
  • Ошибки не возникают ни при первом, ни при втором вызове. но обычно происходит при третьем вызове.
  • Я создаю объект PythonNet, запускаю его, удаляю его и запускаю новый объект PythonNet.
  • Я отметил в коде строки, где происходят ошибки, но не понимаю, почему это срабатывает иногда по 4 раза и иногда только один раз. Это совершенно случайно.
  • Иногда программа завершается с кодом нарушения доступа, не выдавая никаких ошибок.
  • Программа выполняется до конца и затем отключается, причем чаще всего завершается с нарушением прав доступа.
Вопросы:
< ul>
[*]Как правильно сбросить или перезапустить PythonNet между выполнением различных сценариев Python, чтобы избежать эти ошибки доступа к памяти?
[*]Есть ли способ гарантировать, что каждый скрипт Python выполняется в полностью изолированной среде внутри PythonNet?
[*]Что может быть причиной эти нарушения произвольного доступа и как их предотвратить?

Я благодарен за любые предложения или идеи по решению этой проблемы.

Подробнее здесь: https://stackoverflow.com/questions/792 ... ially-in-c
Реклама
Ответить Пред. темаСлед. тема

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

  • Похожие темы
    Ответы
    Просмотры
    Последнее сообщение

Вернуться в «C#»