Python-子进程不发送返回码-stdout流无限期挂起

时间:2018-11-07 18:19:42

标签: python subprocess python-asyncio

我有一个Python脚本,可与控制台应用程序(我公司专有)对接。对于该控制台中的命令,我必须通过Popen打开它,然后通过stdin.write将其传递给命令。我实现了一个非阻塞流读取器,以将控制台标准输出实时流式传输到python控制台。这一切都很好。

问题在于控制台永远不会退出并且不会发送返回代码(即poll()始终返回None,wait()永远挂起)。 因此,我必须处理输出以确定命令何时完成。另一个警告是,我通过stdin.write()发送的命令实际上执行了一个脚本,该脚本又在控制台中执行了一系列命令。控制台将在执行命令时暂停,并且stdout.readline()将立即返回''。所以我不能用它来打破循环。我拥有的代码可以工作99%,但是我无法找到一种方法来优雅地退出,关闭子进程并在NonBlockingStreamReader中终止线程。所以这是代码:

    # nbstreamready.py    - source: http://eyalarubas.com/python-subproc-nonblock.html
    from threading import Thread
    from queue import Queue, Empty

    class NonBlockingStreamReader:

        def __init__(self, stream):
            '''
            stream: the stream to read from.
                    Usually a process' stdout or stderr.
            '''

            self._s = stream
            self._q = Queue()

            def _populateQueue(s, q):
                '''
                Collect lines from 'stream' and put them in 'quque'.
                '''

                while True:
                    line = s.readline()
                    if line:
                        q.put(line)
                    else:
                        raise UnexpectedEndOfStream

            self._t = Thread(target=_populateQueue, args=(self._s, self._q), name='Thread-nsbr')
            self._t.daemon = True
            self._t.start()  # start collecting lines from the stream

        def readline(self, timeout=None):
            try:
                return self._q.get(block=timeout is not None, timeout=timeout)
            except Empty:
                return None

    class UnexpectedEndOfStream(Exception):
        pass

主类:

# etconsole.py
from subprocess import Popen, PIPE, STDOUT
from time import sleep
from nbstreamreader import NonBlockingStreamReader
from paths import scriptFolder




class ETConsole:

            def __init__(self, scriptFileName, etInstallFolder='C:\\Program Files (x86)\\Engineering Tool 2010\\'):
                self.exeFilePath = etInstallFolder + 'ConsoleEngineeringTool.exe'
                self.scriptFile = scriptFolder + scriptFileName

                self.process = Popen(self.exeFilePath, stdin=PIPE, stdout=PIPE, stderr=STDOUT)
                self.nbsr = NonBlockingStreamReader(self.process.stdout)
                self.nbsr.start()


            def getoutput(self):
                while True:
                    if self.process.returncode is not None:
                        print(f'ET Returncode: {self.process.returncode}')
                    if self.process.poll() is not None:
                        print(f'Poll: {self.process.returncode}')
                    output = self.nbsr.readline()
                    if output is not None:
                        outchar = output.decode().rstrip()
                        if 'Result executescript' in outchar:
                            print(outchar)
                            break
                        print(outchar)

            def runscript(self):
                sleep(.5)
                cmd = 'executescript /import ' + self.scriptFile + '\n'
                cmdbytes = bytes(cmd, 'utf-8')
                # print("Command = " + cmd)
                self.process.stdin.write(cmdbytes)
                self.process.stdin.flush()
                self.getoutput()

            def terminate(self):
                self.process.terminate()

因此,当我们在控制台中执行时,getoutput()方法正确地中断了循环读取标准输出。为了清楚起见,我引用了控制台exe中的流式标准输出并将其缩进。

>>>from etconsole import ETConsole
et = ETConsole('RIGM-100012_testscript.txt')
et.runscript()
            "ConsoleET>
            ### 2018-11-07 12:52:55 Command executescript
            ### 2018-11-07 12:52:55 Command setarchitecture
            ### 2018-11-07 12:52:55 Progress setarchitecture "" 0...10...20...30...40...50...60...70...80...90...100
            ### 2018-11-07 12:52:55 OCULAR-START setarchitecture
            ApplicationMode = Tea2
            ### 2018-11-07 12:52:55 OCULAR-END setarchitecture
            ### 2018-11-07 12:52:55 Result setarchitecture 0
            ### 2018-11-07 12:52:55 Command login
            ### 2018-11-07 12:52:55 Progress login "" 0...10...20...30...40...50...60...70...80...90...100
            ### 2018-11-07 12:53:01 OCULAR-START login
            LoginToSews = 0
            ### 2018-11-07 12:53:01 OCULAR-END login
            ### 2018-11-07 12:53:01 Result login 0
            ### 2018-11-07 12:53:02 Command connect
            ### 2018-11-07 12:53:02 Progress connect "" 0...10...20...30...40...50...60...70...80...90...100
            ### 2018-11-07 12:53:02 OCULAR-START connect
            Connection-Status = 0
            ### 2018-11-07 12:53:02 OCULAR-END connect
            ### 2018-11-07 12:53:02 Result connect 0
            ### 2018-11-07 12:53:02 Command disconnect
            ### 2018-11-07 12:53:02 Progress disconnect "" 0...10...20...30...40...50...60...70...80...90...100
            ### 2018-11-07 12:53:02 OCULAR-START disconnect
            Disconnect-Status = 0
            ### 2018-11-07 12:53:02 OCULAR-END disconnect
            ### 2018-11-07 12:53:02 Result disconnect 0
            ### 2018-11-07 12:53:02 Result executescript 0"
>>>

这正是我想要的,但这使子进程(在这种情况下为ConsoleEngineeringTool)保持运行状态(因为我使用stdin.write而不是communication()等),所以让我们手动终止该过程:

>>>et.terminate()
Exception in thread Thread-nsbr:
Traceback (most recent call last):
  File "C:\Program Files\Python\Python36\lib\threading.py", line 916, in _bootstrap_inner
    self.run()
  File "C:\Program Files\Python\Python36\lib\threading.py", line 864, in run
    self._target(*self._args, **self._kwargs)
  File "C:\Users\ru3944v\PycharmProjects\DP3_TestHelper\nbstreamreader_original.py", line 25, in _populateQueue
    raise UnexpectedEndOfStream
nbstreamreader_original.UnexpectedEndOfStream

我尝试使用asyncio创建流读取器link,但是也不起作用,它也在等待poll()返回某些内容。我也尝试过使nbstreamreader中的线程成为可停止的线程,并在主类中调用其stop事件,这也不起作用。

因此,我需要一种优雅/安全的方法来帮助您1.关闭子进程exe和2.关闭nonblockingstreamreader中的线程。

非常感谢。

P.S。我正在使用Windows 7,Python 3.6。是的,我已经做了很多寻找答案的尝试,并尝试了许多不同的方法。

0 个答案:

没有答案