Как запустить подпроцесс, отобразить его вывод в графическом интерфейсе и разрешить его завершение? - PullRequest
3 голосов
/ 16 сентября 2009

Я пытался написать приложение, которое запускает подпроцессы и (среди прочего) отображает их вывод в графическом интерфейсе и позволяет пользователю нажать кнопку, чтобы отменить их. Я запускаю процессы так:

queue = Queue.Queue(500)
process = subprocess.Popen(
    command,
    stdout=subprocess.PIPE,
    stderr=subprocess.STDOUT)
iothread = threading.Thread(
    target=simple_io_thread,
    args=(process.stdout, queue))
iothread.daemon=True
iothread.start()

где simple_io_thread определяется следующим образом:

def simple_io_thread(pipe, queue):
    while True:
        line = pipe.readline()
        queue.put(line, block=True)
        if line=="":
            break

Это работает достаточно хорошо. В моем пользовательском интерфейсе я периодически делаю неблокирующие «get» из очереди. Однако мои проблемы возникают, когда я хочу завершить подпроцесс. (Подпроцесс - это произвольный процесс, а не то, что я написал сам.) Я могу использовать метод terminate для завершения процесса, но я не знаю, как гарантировать, что мой поток ввода-вывода завершится. Обычно он выполняет блокировку ввода / вывода на канале. Это может закончиться или не закончиться через некоторое время после того, как я закончу процесс. (Если подпроцесс породил другой подпроцесс , я могу убить первый подпроцесс, но второй все равно будет держать канал открытым. Я даже не уверен, как заставить таких внуков завершаться чисто. ) После этого поток ввода-вывода попытается поставить в очередь выходные данные, но я не хочу фиксировать чтение из очереди бесконечно.

В идеале я хотел бы, чтобы какой-нибудь способ запросил завершение подпроцесса, блокировку на короткое (<0,5 с) промежуток времени и после этого гарантировалось, что поток ввода-вывода завершился (или завершится своевременно без мешая чему-либо еще) и что я могу прекратить чтение из очереди. </p>

Для меня не критично, что решение использует поток ввода-вывода. Если есть другой способ сделать это, который работает в Windows и Linux с Python 2.6 и графическим интерфейсом Tkinter, это будет хорошо.


РЕДАКТИРОВАТЬ - ответ Уилла и другие вещи, которые я видел в Интернете о том, как сделать это на других языках, предполагают, что операционная система ожидает, что вы просто закроете дескриптор файла в главном потоке, а затем должен выйти поток ввода-вывода о его блокировке читайте. Однако, как я описал в комментарии, это, похоже, не работает для меня. Если я делаю это в главном потоке:

process.stdout.close()

Я получаю:

IOError: close() called during concurrent operation on the same file object.

... в основном потоке. Если я делаю это в главном потоке:

os.close(process.stdout.fileno())

Я получаю:

close failed in file object destructor: IOError: [Errno 9] Bad file descriptor

... позже в основном потоке, когда он пытается закрыть дескриптор файла.

Ответы [ 4 ]

2 голосов
/ 10 мая 2011

Я знаю, что это старый пост, но в случае, если он все еще кому-то помогает, я думаю, что ваша проблема может быть решена путем передачи экземпляра subprocess.Popen в io_thread, а не в его выходной поток. Если вы сделаете это, то можете заменить строку while True: на while process.poll() == None:.

process.poll () проверяет код возврата подпроцесса; если процесс не завершен, то его нет (т. е. process.poll() == None). Затем вы можете покончить с if line == "": break.

Причина, по которой я здесь, заключается в том, что я написал сценарий, очень похожий на этот, и получил: -
IOError: close() called during concurrent operation on the same file object. ошибок.

Опять же, в случае, если это поможет, я думаю мои проблемы проистекают из (моего) io_thread, выполняющего чрезмерно эффективную сборку мусора, и закрывает дескриптор файла, который я ему даю (возможно, я ошибаюсь, но теперь это работает. .) Мой метод отличается тем, что он не демонический и выполняет итерацию по subprocess.stdout, а не по циклу while .. т.е.: -

def io_thread(subprocess,logfile,lock):
    for line in subprocess.stdout:
        lock.acquire()
        print line,
        lock.release()
        logfile.write( line )

Я также, вероятно, должен упомянуть, что я передаю аргумент bufsize в subprocess.Popen, чтобы он был буферизован.

2 голосов
/ 07 января 2014

Это, вероятно, достаточно старый, но все еще полезный для кого-то из поисковой системы ...

Причина, по которой оно показывает это сообщение, заключается в том, что после завершения подпроцесса он закрывает файловые дескрипторы, поэтому поток демона (который работает одновременно) попытается использовать эти закрытые дескрипторы, вызывающие ошибку.

Присоединение к потоку до того, как методы подпроцесса wait () или communication () должны быть более чем достаточными для подавления ошибки.

my_thread.join()
print my_thread.is_alive()
my_popen.communicate()
1 голос
/ 28 августа 2017

Вместо этого вы должны закрыть канал записи ... но когда вы пишете код, вы не можете получить к нему доступ. Для этого нужно

  1. ящик для трубы
  2. передать идентификатор файла канала записи в Popen 's stdout
  3. использовать файл чтения канала simple_io_thread для чтения строк.

Теперь вы можете закрыть канал записи, и поток чтения закроется изящно.

queue = Queue.Queue(500)
r, w = os.pipe()
process = subprocess.Popen(
    command,
    stdout=w,
    stderr=subprocess.STDOUT)
iothread = threading.Thread(
    target=simple_io_thread,
    args=(os.fdopen(r), queue))
iothread.daemon=True
iothread.start()

Теперь

os.close(w)

Вы можете закрыть трубу, и iothread отключится без каких-либо исключений.

1 голос
/ 16 сентября 2009

В коде, который завершает процесс, вы также можете явно os.close() канал, из которого читает ваш поток?

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