Python - подпроцесс не отправляет код возврата - поток stdout висит бесконечно - PullRequest
0 голосов
/ 07 ноября 2018

У меня есть скрипт 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 вместо коммуникации () и т. Д.) Так что давайте прервем процесс вручную:

>>>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 для создания потокового считывателя ссылка , но тоже не работает, он также ожидает в poll () что-то для возврата. Я также попытался сделать поток в nbstreamreader остановимым потоком и вызвать его событие остановки в главном классе, тоже не работает.

Так что мне нужна помощь с элегантным / безопасным способом: 1. закрыть подпроцесс exe и 2. закрыть поток в nonblockingstreamreader.

Большое спасибо.

P.S. Я работаю с Windows 7, Python 3.6. И да, я много искал ответы и пробовал много разных методов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...