Предотвращение блокирования операций чтения с помощью потоков выполнения

predotvrashhenie blokirovaniya operacij chteniya s pomoshhju potokov vypolneniya Приемы программирования графических интерфейсов

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

Пример 10.29 демонстрирует реализацию этого решения. Основная хитрость состоит в том, чтобы отделить операции ввода и вывода в оригинальной функции redirectedGuiShellCmd из модуля guiStreams, представленного в примере 10.12. В той версии операция ввода запускается в параллельном потоке выполнения и не блокирует графический интерфейс. Главный поток графического интерфейса использует цикл обработки событий от таймера after как обычно — чтобы проверять наличие данных в общей очереди, добавляемых потоком чтения. Так как главный поток сам не занимается чтением вывода дочерней программы, он не блокируется в ожидании поступления новых данных.

Пример 10.29. PP4E\Gui\Tools\pipe_gui3.py

читает данные из канала в отдельном потоке выполнения и помещает их в очередь, которая проверяется в цикле обработки событий от таймера; позволяет сценарию отображать вывод программы, не вызывая блокирование графического интерфейса между операциями вывода; со стороны дочерних программ не требуется выполнять подключение или выталкивать буферы, но данное решение сложнее, чем подход на основе сокетов import _thread as thread, queue, os

from tkinter import Tk

from PP4E.Gui.Tools.guiStreams import GuiOutput

stdoutQueue = queue.Queue() # бесконечной длины

def producer(input):

while True:

line = input.readline() # блокирование не страшно: дочерний поток

stdoutQueue.put(line) # пустая строка — конец файла

if not line: break

def consumer(output, root, term=’<end>’):

try:

line = stdoutQueue.get(block=False) # главный поток: проверять очередь

except queue.Empty: # 4 раза в сек, это нормально,

pass # если очередь пуста

else:

if not line: # остановить цикл по достижении конца файла

output.write(term) # иначе отобразить следующую строку return

output.write(line)

root.after(250, lambda: consumer(output, root, term))

def redirectedGuiShellCmd(command, root):

input = os.popen(command, ‘r’) # запустить программу командной строки

output = GuiOutput(root)

thread.start_new_thread(producer, (input,)) # запустить поток чтения consumer(output, root)

if __name__ == ‘__main__’: win = Tk() redirectedGuiShellCmd(‘python -u pipe-nongui.py’, win) win.mainloop()

Здесь мы используем очередь, чтобы избежать необходимости обновления графического интерфейса в дочерних потоках. Обратите внимание, что в предыдущем разделе, в примере с сокетами, очереди и потоки выполнения не требовались лишь потому, что у нас была возможность проверить сокет на наличие данных без блокирования — цикла обработки событий от таймера after было вполне достаточно. Однако при организации обмена данными через канал потоки выполнения являются самым простым способом избежать блокирования графического интерфейса.

Если запустить этот сценарий, программный код самотестирования создаст окно с виджетом ScrolledText, в котором будут отображаться текущие дата и время, отправляемые сценарием pipesnongui.py из примера 10.27. Фактически это окно идентично тем, что создают предыдущие версии (рис. 10.15). Каждые две секунды в окне будет появляться новая строка, потому что именно с такой частотой сценарий pipesnongui выводит сообщения в stdout.

Обратите внимание, что поток-производитель загружает данные по одной строке с помощью метода readline(). Мы не можем использовать функции чтения, которые пытаются загрузить все данные из потока ввода целиком (такие как read(), readlines()), потому что они не возвращают управление, пока программа не завершится и не отправит признак конца файла. Для чтения фрагмента данных можно было бы использовать метод read(N), но в этом случае мы исходим из предположения, что в поток вывода передаются текстовые данные. Обратите также внимание, что здесь снова используется ключ u, запрещающий буферизацию потоков ввода-вывода, чтобы обеспечить получение данных по мере их вывода. Без этого выводимые данные вообще не попали бы в графический интерфейс, потому что сохранялись бы в выходном буфере дочерней программы (попробуйте сами).

Использованная литература:
Марк Лутц — Программирование на Python, 4-е издание, I том, 2011

Оцените статью
Секреты программирования
Добавить комментарий