Работа с внешними процессами

Я работал над графическим приложением, которое должно управлять внешними процессами. Работа с внешними процессами приводит к множеству проблем, которые могут осложнить жизнь программиста. Я чувствую, что обслуживание этого приложения занимает недопустимо много времени. Я пытался перечислить вещи, которые затрудняют работу с внешними процессами, чтобы я мог придумать способы смягчения боли. Этот вид превратился в напыщенную речь, которую я решил опубликовать здесь, чтобы получить некоторую обратную связь и дать руководство для тех, кто думает о плавании в этих очень мутных водах. Вот что у меня так далеко:

  1. Вывод от ребенка может быть перепутан с выводом от родителя. Это может сделать оба выхода вводящими в заблуждение и трудными для чтения. Это может быть трудно сказать, что пришло откуда. Становится все труднее понять, что происходит, когда все происходит асинхронно. Вот надуманный пример:

    import textwrap, os, time
    from subprocess import Popen
    test_path = 'test_file.py'
    
    with open(test_path, 'w') as file:
        file.write(textwrap.dedent('''
            import time
            for i in range(3):
                print 'Hello %i' % i
                time.sleep(1)'''))
    
    proc = Popen('python -B "%s"' % test_path)
    
    for i in range(3):
        print 'Hello %i' % i
        time.sleep(1)
    
    os.remove(test_path)
    

    Выход:

    Hello 0
    Hello 0
    Hello 1
    Hello 1
    Hello 2
    Hello 2
    

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

    Если у меня есть код для дочернего процесса, я мог бы добавить метку, что-то вроде print 'child: Hello %i', но это может быть раздражающим, чтобы сделать это для каждого отпечатка. И это добавляет шум на выходе. И, конечно, я не могу этого сделать, если у меня нет доступа к коду.

    Я мог бы вручную управлять процессом вывода. Но затем вы открываете огромную банку с червями с нитями, опросами и тому подобным.

    Простое решение состоит в том, чтобы рассматривать процессы как синхронные функции, то есть никакой дальнейший код не выполняется до тех пор, пока процесс не завершится. Другими словами, сделать блок процесса. Но это не работает, если вы создаете приложение с графическим интерфейсом. Что подводит меня к следующей проблеме...

  2. Процессы блокировки приводят к тому, что графический интерфейс перестает отвечать на запросы.

    import textwrap, sys, os
    from subprocess import Popen
    
    from PyQt4.QtGui import *
    from PyQt4.QtCore import *
    
    test_path = 'test_file.py'
    with open(test_path, 'w') as file:
        file.write(textwrap.dedent('''
            import time
            for i in range(3):
                print 'Hello %i' % i
                time.sleep(1)'''))
    
    app = QApplication(sys.argv)
    button = QPushButton('Launch process')
    def launch_proc():
        # Can't move the window until process completes
        proc = Popen('python -B "%s"' % test_path)
        proc.communicate()
    button.connect(button, SIGNAL('clicked()'), launch_proc)
    button.show()
    app.exec_() 
    os.remove(test_path)
    

    Qt предоставляет собственную оболочку процесса, которая называется QProcess который может помочь с этим. Вы можете подключить функции к сигналам, чтобы захватить вывод относительно легко. Это то, что я сейчас использую. Но я обнаружил, что все эти сигналы ведут себя подозрительно goto заявления и может привести к спагетти код. Я думаю, что я хочу получить своего рода блокирующее поведение, когда "готовый" сигнал из QProcess вызывает функцию, содержащую весь код, который идет после вызова процесса. Я думаю, что это должно сработать, но я все еще немного неясен в деталях...

  3. Трассировки стека прерываются, когда вы переходите от дочернего процесса обратно к родительскому процессу. Если нормальная функция облажается, вы получите хороший полный след стека с именами файлов и номерами строк. Если подпроцесс испортится, вам повезет, если вы вообще получите какой-либо вывод. Вы заканчиваете тем, что должны делать намного больше детективной работы каждый раз, когда что-то идет не так.

  4. Говоря об этом, выход имеет способ исчезнуть при работе с внешними процессами. Например, если вы запускаете что-то с помощью команды windows 'cmd', консоль выскочит, выполнит код и затем исчезнет, ​​прежде чем вы сможете увидеть результат. Вы должны передать флаг / k, чтобы он остался. Подобные проблемы, кажется, возникают постоянно.

    Я полагаю, что обе проблемы 3 и 4 имеют одну и ту же основную причину: нет обработки исключений. Обработка исключений предназначена для использования с функциями, она не работает с процессами. Может быть, есть какой-то способ получить что-то вроде обработки исключений для процессов? Полагаю, для этого и нужен stderr? Но работа с двумя различными потоками может быть раздражающей сама по себе. Может быть, я должен посмотреть на это больше...

  5. Процессы могут зависать на заднем плане, даже не подозревая об этом. Таким образом, вы в конечном итоге кричите на свой компьютер, потому что он будет работать так медленно, что, наконец, вы не откроете свой диспетчер задач и не увидите 30 экземпляров одного и того же процесса в фоновом режиме.

    Кроме того, зависание фоновых процессов может мешать другим экземплярам процесса различными забавными способами, например вызывать ошибки прав доступа, удерживая дескриптор файла или что-то подобное.

    Кажется, что простым решением для этого было бы, чтобы родительский процесс убил дочерний процесс при выходе, если дочерний процесс не закрылся сам. Но если происходит сбой родительского процесса, код очистки может не вызываться, а дочерний процесс может оставаться без изменений.

    Кроме того, если родитель ожидает завершения дочернего процесса, а дочерний процесс находится в бесконечном цикле или что-то в этом роде, вы можете получить два зависающих процесса.

    Эта проблема может быть связана с проблемой 2 для дополнительного удовольствия, в результате чего ваш графический интерфейс перестает отвечать на запросы полностью и вынуждает вас убивать все с помощью диспетчера задач.

  6. F *** цитаты

    Параметры часто необходимо передавать процессам. Это головная боль сама по себе. Особенно, если вы имеете дело с путями к файлам. Скажи... 'C:/ Мои документы / что угодно /'. Если у вас нет кавычек, строка часто будет разделяться пробелом и интерпретироваться как два аргумента. Если вам нужны вложенные кавычки, вы можете использовать 'и ". Но если вам нужно использовать более двух слоев кавычек, вам нужно сделать несколько неприятных экранировок, например: "cmd /k 'python \'path 1\' \' путь 2\ "

    Хорошим решением этой проблемы является передача параметров в виде списка, а не одной строки. Подпроцесс позволяет вам сделать это.

  7. Не могу легко вернуть данные из подпроцесса.

    Вы можете использовать стандартный вывод конечно. Но что, если вы хотите добавить отпечаток для отладки? Это испортит родитель, если он ожидает, что выходные данные отформатированы определенным образом. В функциях вы можете напечатать одну строку и вернуть другую, и все работает просто отлично.

  8. Непонятные флаги командной строки и дрянная справочная система на основе терминала.

    Это проблемы, с которыми я часто сталкиваюсь при использовании приложений на уровне ОС. Как и флаг / k, который я упомянул, для того, чтобы держать окно cmd открытым, кто это задумал? Unix-приложения в этом отношении не слишком дружелюбны. Надеемся, что вы можете использовать Google или Stackru, чтобы найти ответ, который вам нужен. Но если нет, у вас есть много скучного чтения и бесполезных проб и ошибок.

  9. Внешние факторы.

    Этот вид нечеткий. Но когда вы покидаете относительно защищенную гавань своих собственных сценариев для работы с внешними процессами, вы обнаруживаете, что сталкиваетесь с "внешним миром" в гораздо большей степени. И это страшное место. Все виды вещей могут пойти не так. Просто чтобы привести случайный пример: cwd, в котором запущен процесс, может изменить его поведение.

Возможно, есть и другие проблемы, но это те, которые я записал до сих пор. Какие-нибудь другие препятствия, которые вы хотели бы добавить? Есть предложения по решению этих проблем?

1 ответ

Решение

Проверьте модуль подпроцесса. Это должно помочь с разделением вывода. Я не вижу способа обойтись ни в отдельных выходных потоках, ни в каких-либо тегах вывода в одном потоке.

Проблема с процессом подвешивания также является сложной. Единственное решение, которое мне удалось сделать, - это установить таймер на внешний процесс и убить его, если он не вернется в отведенное время. Грубый, противный, и если у кого-то еще есть хорошее решение, я бы хотел услышать его, чтобы я тоже мог его использовать.

Единственное, что вы можете сделать, чтобы помочь решить проблему полностью неуправляемого завершения работы, - это сохранить каталог pid-файлов. Всякий раз, когда вы запускаете внешний процесс, записывайте файл в каталог файлов pid с именем, которое является pid для процесса. Удалите файл pid, когда вы знаете, что процесс завершился корректно. Вы можете использовать материал в каталоге pid для очистки при сбоях или перезапусках.

Это может не дать каких-либо удовлетворительных или полезных ответов, но, возможно, это начало.

Другие вопросы по тегам