Преамбула написания этого поста такова: на Хабре прочёл о замечательном способе подготовки электронных книг с помощью Linux-утилит пакета psutils. В качестве пробы пера в программировании на Python захотелось написать фронтэнд к psbook и psnup. Тут-то я и наткнулся на проблему, вынесенную в заглавие данной статьи.

Дело в том, что я организовывал вызов вышеупомянутых команд посредством os.system(), которая свою работу делала, но замораживала UI настолько, что его обновление происходило уже после того, как все вызовы os.system() были выполнены.
Первоначально обновление UI было организовано в лоб:
...
os.system('psbook -s%d \"%s\" \"%s\"' %(pagescount, workingfile, tmpfile))
progressbar.set_fraction(frac+0.2) # Всего будет выполняться 5 команд, поэтому прибавляем 0.2
os.system('psnup -2 -P a5 -p a4 -m %d -b %d \"%s\" > \"%s\"' %(marginvalue, bordervalue, tmpfile, workingfile))
progressbar.set_fraction(frac+0.2)
...

Номер не прошёл. Progressbar обновлялся только после выполнения последнего вызова os.system(). Наткнувшись на статью на PyGTK FAQ об обновлении progressbar и параллельного выполнения работы, я перепробовал все указанные в ней варианты, однако ни один не решил моей проблемы. Появилось подозрение, что дело в os.system(). Выход был найден достаточно простой, но не совсем очевидный:

def do_work(self,*args):
...
command_list = ('pdf2ps \"%s\" \"%s\"' %(filename, workingfile),'psbook -s%d \"%s\" \"%s\"'  %(pagescount, workingfile, tmpfile),'psnup -2 -P a5 -p a4 -m %d -b %d \"%s\" > \"%s\"' %(marginvalue, bordervalue, tmpfile, workingfile),'ps2pdf \"%s\" \"%s\"' %(workingfile, outputfile),'rm -f  \"%s\" \"%s\"' %(tmpfile, workingfile))

message_list = ('Выполняем конвертацию в PostScript...','Разбиваем на тетеради...','Собираем брошюру...','Выполняем конвертацию в PDF...','Работы выполнена...')
		
step = 0
self.progressbar.set_fraction(step)

for i in range(len(command_list)):
    step = step + 1/len(command_list)
    self.progressbar.set_text(message_list[i])						
    self.progressbar.set_fraction(step)
    print "Executing command: %s" %command_list[i]
    proc = Popen(command_list[i], shell=True, stdin=PIPE, stdout=PIPE, close_fds=True)
        while proc.poll() is None:
            while gtk.events_pending():
                gtk.main_iteration()
            time.sleep(0.1)
        yield True
self.progressbar.set_fraction(1)
yield False

#making book	
def make_book (self, widget):
...		
    task = self.do_work()			
    glib.idle_add(task.next)
...



В этом коде используется генератор do_work, который, по сути дела, представляет собой псевдо-поток, позволяющий выполнить то, что нам нужно и «не навлечь на себя гнев богов программного обеспечения», как написано в PyGTK FAQ. Подробнее об этом можно прочесть тут. Команды-обработчики файла вызываются как подпроцесс с помощью subpocess.Popen(). subpocess.Popen.poll() проверяет, не завершился ли запущенный процесс. Если результатом проверки является None, то происходит обновление UI.

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

Пользователи Ubuntu, желающие протестировать результат, могут подключить мой PPA и установить пакет pymakebook:
sudo add-apt-repository ppa:telenga/ppa
sudo aptutide update
sudo aptitude install pymakebook

Исходный код можно найти тут.