繁体   English   中英

如何从交互过程的std同步编写和异步读取

[英]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的评论,我试图弄清楚“人类将如何解决这个问题”

  1. 我必须确保子进程仅通过stdOut与父进程进行通信。
  2. 我必须创建一个基于消息的协议,以确保子python进程始终报告他是否已接收并理解了父c#进程发送的消息,如果是,则报告表达式的评估值。
  3. 我必须找到一种同步后续写作和阅读的方法

通过定义一个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并可以同步:

  • 检查python是否在超时之前完成(如果没有,以某种方式做出反应)
  • 如果没有超时发生,我知道_hasError会告诉您评估是否成功
  • 如果是这种情况,则outputMessage将结果包含在倒数第二行。

为了解决所有可监督的问题,我还将编写一个OnErrorDataReceived方法,该方法将从python进程捕获未处理的异常和语法错误,并且可能会引发异常,因为这在我看来永远不会发生。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM