Почему текстовый виджет Tkinter отстает от обновлений при увеличении высоты окна?

В настоящее время я реализую образец пользовательского интерфейса для neovim и решил использовать Tkinter/python из-за популярности/простоты платформы. Проблема, с которой я сталкиваюсь, заключается в том, что tkinter, кажется, «складывает» обновления пользовательского интерфейса, когда высота окна пересекает определенный порог.

Вот видео, демонстрирующее проблему.

Правое окно — это эмулятор терминала под управлением neovim, а левое — подключенная к нему программа Tkinter UI. Идея состоит в том, что пользовательский интерфейс tkinter должен отражать экран терминала neovim, включая размеры. В этом видео никогда не отвлекайте внимание от окна терминала, поэтому единственные события, которые должен обрабатывать Tk, исходят от подключения к neovim (виртуальные события «nvim», которые описывают обновления экрана)

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

Вот код программы Tkinter. Хотя neovim API очень новый и все еще находится в активной разработке (код может быть непонятен для некоторых читателей), я думаю, что проблема, которую я пытаюсь решить, близка к реализации эмулятора терминала (с использованием текстового виджета Tk): он должен обрабатывать большие Пакеты обновлений форматированного текста эффективно.

Я очень неопытен в программировании GUI. Является ли Tkinter мудрым выбором для этой задачи? Если да, то может ли кто-нибудь подсказать, что я делаю неправильно?

Чтобы немного объяснить, что происходит: Neovim API является потокобезопасным, а метод vim.next_event() блокируется (без активного ожидания, он использует цикл событий libuv внизу) до тех пор, пока не будет получено событие.

Когда вызов vim.next_event() возвращается, он уведомляет поток Tkinter, используя generate_event, который будет выполнять фактическую обработку событий (он также буферизует события между redraw:start и redraw:stop для оптимизации обновлений экрана).

Таким образом, на самом деле есть два цикла событий, работающих параллельно, причем цикл фоновых событий передает цикл событий Tkinter потокобезопасным способом (метод generate_event — один из немногих, которые можно вызывать из других потоков).


person Thiago de Arruda    schedule 18.06.2014    source источник
comment
Вы знаете, что Tk обновляет рисунок только тогда, когда цикл событий простаивает? (например, когда after idle будет срабатывать). Вы можете просто заполнить цикл событий большим количеством событий, чтобы перерисовка происходила только после того, как всплеск прошел. Добавьте к этому, что вы искусственно ограничиваете свой объект Queue размером 1, поэтому с ужасной многопоточностью Python вы платите много затрат на изменение потока повсюду.   -  person schlenk    schedule 19.06.2014


Ответы (2)


Я бы дважды проверил, что на самом деле это Tkinter. Я бы сделал это, просто написав на терминал, когда вы получите событие.

Но теперь, когда я присмотрюсь, это может быть вашей проблемой:

    t = Thread(target=get_nvim_events, args=(self.nvim_events,
                                             self.vim,
                                             self.root,))

Потоки плохо взаимодействуют с циклами событий, один из которых уже есть в Tkinter. Я не уверен, настроен ли API-интерфейс neovim на использование обратных вызовов, но обычно именно так вы хотите распространять изменения.

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

while True:
    if something_to_do:
        do_it_now()

Очевидно, что это загруженный цикл, который сожжет ваш ЦП, поэтому обычно ваш цикл событий будет блокировать или настраивать обратные вызовы с помощью ОС, что позволяет ей освобождать ЦП, и когда произойдет что-то интересное, ОС скажет что-то вроде: «Кто-то щелкнул здесь». », или «Кто-то нажал клавишу», или «Эй, ты сказал мне разбудить тебя сейчас же!»

Таким образом, ваша работа как разработчика графического интерфейса состоит в том, чтобы подключиться к этому циклу событий. Вам все равно, когда что-то происходит — вы просто хотите отреагировать на это. С Tkinter вы можете сделать это с помощью .after методов см. "обратные вызовы без событий". Что может быть хорошим выбором, так это метод .after_idle:

Регистрирует обратный вызов, который вызывается, когда система простаивает. Обратный вызов будет вызван, если в основном цикле больше нет событий для обработки. Обратный вызов вызывается только один раз для каждого вызова after_idle.

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

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

def do_something(arg):
    # do something with `arg` here


def event_happened(event_args): #whatever args the event generates
    root.after_idle(lambda: do_something(event_args))

vim.bind("did_something", event_happened)

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

person Wayne Werner    schedule 18.06.2014
comment
Я обновил вопрос, чтобы лучше объяснить, что происходит. - person Thiago de Arruda; 19.06.2014

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

person Community    schedule 30.05.2017
comment
Мне кажется, что это ответ: иногда ты не можешь, вот ответ. Ответ мог бы быть намного лучше, если бы он говорил о том, какие аспекты tk обеспечивают эту производительность, но я думаю, что это действительно отвечает на вопрос. - person Sam Hartman; 31.05.2017