Я использую 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?
[*]Что может быть причиной эти нарушения произвольного доступа и как их предотвратить?
Я благодарен за любые предложения или идеи по решению этой проблемы.
Я использую PythonNet в приложении C# для последовательного запуска нескольких сценариев Python, каждый из которых содержится в разных модулях. Однако я сталкиваюсь с непоследовательными ошибками MemoryAccessViolation, которые иногда возникают во время выполнения программы, при завершении работы или даже в случайное время. Эти ошибки не следуют определенному шаблону, и их трудно надежно воспроизвести. Когда я повторно использую один и тот же экземпляр PythonNet для запуска разных сценариев, возникают ошибки. Попытка завершить работу и перезагрузить экземпляр PythonNet также приводит к этим проблемам доступа к памяти. Кроме того, эти ошибки не перехватываются блоками try-catch, и программа часто аварийно завершает работу с кодом ошибки нарушения прав доступа. Вот соответствующий фрагмент кода, показывающий, как я инициализирую и удаляю Среда Python: [code] /// /// Manages the Python environment using pythonnet. /// public class PythonNetEnv : IDisposable { /// /// Indicates if the environment is initialized. /// public bool IsInitialized { get; private set; }
/// /// 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;
// 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)");
// 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");
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. } }
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);
} } } [/code] [b]Дополнительная информация[/b]: [list] [*]Ошибки не возникают ни при первом, ни при втором вызове. но обычно происходит при третьем вызове. [*]Я создаю объект PythonNet, запускаю его, удаляю его и запускаю новый объект PythonNet. [*] Я отметил в коде строки, где происходят ошибки, но не понимаю, почему это срабатывает иногда по 4 раза и иногда только один раз. Это совершенно случайно. [*]Иногда программа завершается с кодом нарушения доступа, не выдавая никаких ошибок. [*]Программа выполняется до конца и затем отключается, причем чаще всего завершается с нарушением прав доступа. [/list] [b]Вопросы[/b]: < ul> [*]Как правильно сбросить или перезапустить PythonNet между выполнением различных сценариев Python, чтобы избежать эти ошибки доступа к памяти? [*]Есть ли способ гарантировать, что каждый скрипт Python выполняется в полностью изолированной среде внутри PythonNet? [*]Что может быть причиной эти нарушения произвольного доступа и как их предотвратить?
Я благодарен за любые предложения или идеи по решению этой проблемы.
Я использую PythonNet в приложении C# для последовательного запуска нескольких сценариев Python, каждый из которых содержится в разных модулях. Однако я сталкиваюсь с непоследовательными ошибками MemoryAccessViolation, которые иногда возникают во...
Я запускаю класс в более крупном приложении, в котором используется Pythonnet для вызова функций из сценария Python. Сначала я запускаю эти линии, если двигатель Python уже не был инициализирован -
Runtime.PythonDLL = @...
Я запускаю класс в более крупном приложении, в котором используется Pythonnet для вызова функций из сценария Python. Сначала я запускаю эти линии, если двигатель Python уже не был инициализирован -
Runtime.PythonDLL = @...
У меня есть специальный класс Timer на C++, который использует std::async для запуска таймера в отдельном потоке. Проблема, с которой я столкнулся, заключается в том, что когда я последовательно и без задержки вызываю start() для одного и того же...
У меня есть специальный класс Timer на C++, который использует std::async для запуска таймера в отдельном потоке. Проблема, с которой я столкнулся, заключается в том, что когда я последовательно и без задержки вызываю start() для одного и того же...