Работа с внешними процессами - PullRequest
3 голосов
/ 23 мая 2010

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

  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 и могут привести к спагетти-коду. Я думаю, что хочу получить своего рода блокирующее поведение, когда сигнал «Finished» от QProcess вызывает функцию, содержащую весь код, который идет после вызова процесса. Я думаю, что это должно сработать, но я все еще немного неясен в деталях ...

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

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

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

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

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

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

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

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

  6. F *** кавычки

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

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

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

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

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

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

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

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

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

1 Ответ

2 голосов
/ 24 мая 2010

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

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

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

Возможно, это не даст удовлетворительных или полезных ответов, но, возможно, это начало.

...