首页 > 解决方案 > 在无限循环中通过 C# 调用的 Python 不返回值

问题描述

我目前正在实现一个连接到 Raspberry SenseHat 的 .Net 应用程序。为此,我使用 Python 实现https://pythonhosted.org/sense-hat/并通过 Processes 调用 Python 脚本以尽可能松耦合。一切正常,但我的操纵杆有一些问题:该示例在 Python 脚本中使用了无限循环。我的“Joystock.py”脚本目前看起来像这样:

import sys
try:
   import queue
except ImportError:
   import Queue as queue
import threading
import requests

from sense_hat import SenseHat
sense = SenseHat()

# Taken from https://stackoverflow.com/questions/48429653/python-returning-values-from-infinite-loop-thread
def _listen(queue):
  while True:
    event = sense.stick.wait_for_event(emptybuffer=True)
    val = event.action + ":" + event.direction
    queue.put(val)

def listen(params):
  q = queue.Queue()
  t1 = threading.Thread(target=_listen, name=_listen, args=(q,))
  t1.start()

  while True:
    value = q.get()
    print(value)

if __name__ == '__main__':
  args = sys.argv
  args.pop(0) # Remove file path
  methodName = args.pop(0) # Pop method name

  globals()[methodName](args)

底部是传递方法名称和我想通过参数调用的参数。我的 C# 调用如下所示:

public void Listen(PythonListeningRequest request)
{
    var startInfo = _startInfoFactory.CreateForListening(request);

    var process = Process.Start(startInfo);
    process.BeginErrorReadLine();
    process.BeginOutputReadLine();
    process.EnableRaisingEvents = true;
    process.OutputDataReceived += (object sender, DataReceivedEventArgs e) =>
    {
        Console.WriteLine("Input: " + e.Data);
    };

    process.ErrorDataReceived += (object sender, DataReceivedEventArgs e) =>
    {
        Console.WriteLine("Error: " + e.Data);
    };
}

以及 ProcessStartInfo 的定义:

public ProcessStartInfo CreateForListening(PythonRequest request)
{
    return new ProcessStartInfo
    {
        FileName = FindPythonExeFilePath(),
        Arguments = CreateArgumentsString(request),
        UseShellExecute = false,
        RedirectStandardInput = true,
        RedirectStandardOutput = true,
        RedirectStandardError = true,
        CreateNoWindow = true,
        WindowStyle = ProcessWindowStyle.Hidden
    };
}

private static string CreateArgumentsString(PythonRequest request)
{
    var sb = new StringBuilder();
    sb.Append(request.FilePath);
    sb.Append(" ");

    sb.Append(request.MethodName);
    sb.Append(" ");

    foreach (var arg in request.Arguments)
    {
        sb.Append(arg.AsString());
        sb.Append(" ");
    }

    var result = sb.ToString();
    return result;
}

private string FindPythonExeFilePath()
{
    var possibleFilePaths = new string[]
    {
        @"C:\Users\mlm\AppData\Local\Programs\Python\Python37-32\python.exe",
        @"C:\WINDOWS\py.exe",
        "/usr/bin/python"
    };

    var existingPythonPath = possibleFilePaths.FirstOrDefault(fp => _fileSystem.File.Exists(fp));
    Guard.That(() => existingPythonPath != null, "No python path found.");

    return existingPythonPath;
}

正如您在 python 部分中看到的那样,使用了一个队列,这是我从另一个 SO 问题中得到的。不幸的是,它仍然不起作用,只要代码中有“t1.start()”,我就永远不会得到返回值。

手动尝试 python 脚本工作正常,所以我猜问题是进程连接到 C#?不幸的是,我没有找到与此行为相关的任何内容,因此有人知道,可能导致此问题的原因是什么?

标签: c#python

解决方案


底线:在任一流上使用sys.stdoutsys.stderr紧随其后flush()并避免print

由于我无法拥有 SenseHat,因此我将您的示例缩小为:

try:
    import queue
except ImportError:
    import Queue as queue
import threading
import time
import sys

# Taken from https://stackoverflow.com/questions/48429653/python-returning-values-from-infinite-loop-thread
def _listen(queue):
    val =0
    while True:
        time.sleep(1)
        val = val+1
        queue.put(val)


def listen(params):
    q = queue.Queue()
    t1 = threading.Thread(target=_listen, name=_listen, args=(q,))
    t1.start()

    while True:
        value = q.get()
        sys.stdout.write(str(value) + '\n')
        sys.stdout.flush()


if __name__ == '__main__':
    args = sys.argv
    args.pop(0)  # Remove file path
    methodName = args.pop(0)  # Pop method name

    globals()[methodName](args)

至于 C# 部分,我没有改变任何东西,只是摆脱了class PythonRequest

这似乎有效。而print(value)不是sys.stdout.write(str(value) + '\n') sys.stdout.flush()我没有从回调中获得任何返回值OutputDataReceived

因此,我相信您必须在通过管道传输到 C# 的流上进行写入sys.stdoutsys.stderr然后强制写入。flush否则 usingprint填充标准输出缓冲区并且不一定刷新。


推荐阅读