![](/img/trans.png)
[英]How to synchronize reading and writing to a NetworkStream from different Tasks?
[英]How to synchronize writing and async reading from interactive process's std's
我正在尝试构建一个使用pythons动态解释器及其eval函数的wpf应用程序。 编辑 :我已经在这里给出了更详细的描述,简单来说,我希望能够执行以下操作:
string expression = Console.ReadLine("Please enter your expression");
if (EvaluateWithPythonProcess(expression) > 4)
{
// Do something
}
else
{
// Do something else
}
由于我的程序在整个生命周期中都使用此功能,因此我每次想开始评估时都无法退出python进程。 因此,StdIn,StdOut和StdErr流始终保持打开状态。
我能够使用Process类和两个相应的OnOutputDataReceived和OnErrorDataReceived方法(将数据从stdOut和stdErr传输到StringBuilders中)启动交互式python.exe:
// 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是否已完成对表达式的求值。 一种可能的解决方案是在向python 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)");
我发现一个解决方案对我来说是针对我的特定方案的一种解决方法,而不是一般的解决方案。
根据@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是通过特定的方法手动重置的,该方法会将命令发送给python,在waitOne完成后(发生超时之前)自动进行,并在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进程捕获未处理的异常和语法错误,并且可能会引发异常,因为这在我看来永远不会发生。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.