我正在尝试构建一个使用pythons动态解释器及其eval函数的wpf应用程序。 编辑:我已经提供了更详细的说明here简单来说,我希望能够执行以下操作:
string expression = Console.ReadLine("Please enter your expression");
if (EvaluateWithPythonProcess(expression) > 4)
{
// Do something
}
else
{
// Do something else
}
由于我的程序在整个生命周期中使用此功能,因此每次我想开始评估时都无法退出python进程。因此,StdIn,StdOut和StdErr Streams始终保持开放状态。
我能够使用Process类和两个相应的OnOutputDataReceived和OnErrorDataReceived方法启动交互式python.exe,这些方法将数据从stdOut和stdErr传输到StringBuilders:
// create the python process StartupInfo object
ProcessStartInfo _processStartInfo = new ProcessStartInfo(PythonHelper.PathToPython + "python.exe");
// python uses "-i" to run in interactive mode
_processStartInfo.Arguments = "-i";
// Only start the python process, but don't show a (console) window
_processStartInfo.WindowStyle = ProcessWindowStyle.Minimized;
_processStartInfo.CreateNoWindow = true;
// Enable the redirection of python process std's
_processStartInfo.UseShellExecute = false;
_processStartInfo.RedirectStandardOutput = true;
_processStartInfo.RedirectStandardInput = true;
_processStartInfo.RedirectStandardError = true;
// Create the python process object and apply the startupInfos from above
_pythonProcess = new Process();
_pythonProcess.StartInfo = _processStartInfo;
// Start the process, _hasStarted indicates if the process was actually started (true) or if it was reused (false, was already running)
_pythonProcess.OutputDataReceived += new DataReceivedEventHandler(OnOutputDataReceived);
_pythonProcess.ErrorDataReceived += new DataReceivedEventHandler(OnErrorDataReceived);
bool _hasStarted = _pythonProcess.Start();
_pythonProcess.BeginOutputReadLine();
_pythonProcess.BeginErrorReadLine();
_input = _pythonProcess.StandardInput;
但是,我无法通过此异步结果收集来同步我的应用程序。由于两个On * DataReceived()方法是异步调用的,我不知道python是否已完成对表达式的求值。一个可能的解决方案是在向pythons stdIn发送命令之前创建一个等待句柄,之后我可以等待。 OnOutputDataReceived和OnErrorDataReceived方法都可以发出此句柄的信号。但是,这在某种程度上被python的预期行为所掩盖:
// example A: Import the sys modul in python
// this does cause neither an output, nor an error:
_input.WriteLine("import sys");
// example B: Writing to pythons stderr or stdout results in Error AND Output, how can I tell if an error occured?
_input.WriteLine("sys.stderr.write('Initialized stdErr')");
_input.WriteLine("sys.stdout.write('Initialized stdOut')");
// example C: This is the intended use, but how can I tell if evaluation has finished succesfully?
_input.WriteLine("print(4+7)");
// example D: A simple typo might lead to unforeseeable errors but how can I tell if evaluation has finished succesfully?
_input.WriteLine("pr int(4+7)");
答案 0 :(得分:0)
我找到了一个解决方案,我认为这是针对我的具体方案的解决方法而不是一般解决方案。
根据@Peter的评论,我试图弄清楚“人类会如何解决这个问题”
通过定义一个永远是我父进程的目标的python方法来实现第一点和第二点。我使用pythons异常处理例程来检测错误并防止它写入stdErr,如下所示:
def PythonEval(expression):
"Takes an expression and uses the eval function, wrapped in a try-except-statement, to inform the parent process about the value of this expression"
try:
print(eval(expression))
print('CHILD: DONE')
except:
print('CHILD: ERROR')
return
通过将python代码包装在字符串中并将其传递给子进程的stdIn,可以在我的c#应用程序中应用此定义:
childInput = childProcess.StandardInput;
childInput.WriteLine("def PythonEval(expression):\n" +
"\t\"Takes an expression and uses the eval function, wrapped in a try-except-clause, to inform the LMT about the outcome of this expression\"\n" +
"\ttry:\n" +
"\t\tprint(eval(expression))\n" +
"\t\tprint('" + _pythonSuccessMessage + "')\n" +
"\texcept:\n" +
"\t\tprint('" + _pythonErrorMessage + "')\n" +
"\treturn\n" +
"\n"); // This last newline character is important, it ends the definition of a method if python is in interactive mode
如果我想在子进程的帮助下计算表达式,那么父进程现在必须将表达式包装在这个python方法的相应调用中:
childInput.WriteLine("PythonEval('" + expression + "')");
这将在所有情况下导致向子进程的stdOut发送消息,该消息具有“CHILD:DONE | ERROR”形式的最后一行,我可以将其与之比较并在后一种情况下设置一个布尔标志_hasError。整个消息传递给stringBuilder OutputMessage。
当子进程将此消息发送到其stdOut时,将触发C#进程对象的OutputDataReceivedEvent,并通过OnOutputDataReceived方法异步读取数据。为了与进程的asnyc读取操作同步,我使用AutoResetEvent。它允许将父c#进程与python进程同步,并通过使用AutoResetEvent.WaitOne(int timeout)重载来防止死锁。
AutoResetEvent在一个特定的方法中手动重置,该方法在waitOne完成之后(在发生超时之前)自动将命令发送到python,并在async OnOutputDataReceived()方法中手动设置,如下所示:
private AutoResetEvent _outputResetEvent;
private bool _hasError;
private StringBuilder _outputMessage;
private void EvaluateWithPython(string expression)
{
// Set _outputResetEvent to unsignalled state
_outputResetEvent.Reset();
// Reset _hasError,
_hasError = true;
// Write command to python, using its dedicated method
childInput.WriteLine("PythonEval('" + expression + "')"); // The ' chars again are important, as the eval method in python takes a string, which is indicated by 's in python
// wait for python to write into stdOut, which is captured by OnOutputDataReceived (below) and sets _outputResetEvent to signalled stat
bool _timeoutOccured = _outputResetEvent.WaitOne(5000);
// Give OnOutputDataReceived time to finish
Task.Delay(200).Wait();
}
private void OnOutputDataReceived(object sender, DataReceivedEventArgs e)
{
if (e == null)
{
throw new ArgumentNullException();
}
if (e.Data != null)
{
// Pass message into StringBuilder line by line, as OnOutputDataReceived is called line by line
_outputMessage.AppendLine(e.Data);
// Check for end of message, this is in all cases of the form "CHILD: DONE|ERROR"
// In this case, set errorFlag if needed and signal the AutoResetEvent
if (e.Data.Equals("CHILD: ERROR"))
{
_hasError = true;
_outputResetEvent.Set();
}
else if (e.Data.Equals("CHILD: DONE"))
{
_hasError = false;
_outputResetEvent.Set();
}
}
else
{
// TODO: We only reach this point if child python process ends and stdout is closed (?)
_outputResetEvent.Set();
}
}
通过这种方法,我可以调用EvaluateWithPython并且可以同步:
为了应对任何监督问题,我还将编写一个OnErrorDataReceived方法,该方法将从python进程中捕获未处理的异常和语法错误,例如,可能抛出异常,因为这应该在我看来永远不会发生。