Tkinter блокирует Python, когда загружается значок, а tk.mainloop находится в потоке

Вот тестовый пример...

import Tkinter as tk
import thread
from time import sleep

if __name__ == '__main__':
    t = tk.Tk()
    thread.start_new_thread(t.mainloop, ())
    # t.iconbitmap('icon.ico')

    b = tk.Button(text='test', command=exit)
    b.grid(row=0)

    while 1:
        sleep(1)

Этот код работает. Раскомментируйте строку t.iconbitmap, и она заблокируется. Перестройте его так, как вам нравится; он заблокируется.

Как предотвратить блокировку tk.mainloop GIL при наличии значка?

Цель — win32 и Python 2.6.2.


person burito    schedule 29.07.2009    source источник


Ответы (1)


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

Инструментарий графического интерфейса, с которым я знаком (Tkinter, .NET Windows Forms), таков: вы можете управлять графическим интерфейсом только из одного потока.

В Linux ваш код вызывает исключение:

self.tk.mainloop(n)
RuntimeError: Calling Tcl from different appartment

Один из следующих будет работать (без дополнительных потоков):

if __name__ == '__main__':
    t = tk.Tk()
    t.iconbitmap('icon.ico')

    b = tk.Button(text='test', command=exit)
    b.grid(row=0)

    t.mainloop()

С дополнительной нитью:

def threadmain():
    t = tk.Tk()
    t.iconbitmap('icon.ico')
    b = tk.Button(text='test', command=exit)
    b.grid(row=0)
    t.mainloop()


if __name__ == '__main__':
    thread.start_new_thread(threadmain, ())

    while 1:
        sleep(1)

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

Вот пример:

import Tkinter as tk
import thread
from time import sleep
import Queue

request_queue = Queue.Queue()
result_queue = Queue.Queue()

def submit_to_tkinter(callable, *args, **kwargs):
    request_queue.put((callable, args, kwargs))
    return result_queue.get()

t = None
def threadmain():
    global t

    def timertick():
        try:
            callable, args, kwargs = request_queue.get_nowait()
        except Queue.Empty:
            pass
        else:
            print "something in queue"
            retval = callable(*args, **kwargs)
            result_queue.put(retval)

        t.after(500, timertick)

    t = tk.Tk()
    t.configure(width=640, height=480)
    b = tk.Button(text='test', name='button', command=exit)
    b.place(x=0, y=0)
    timertick()
    t.mainloop()

def foo():
    t.title("Hello world")

def bar(button_text):
    t.children["button"].configure(text=button_text)

def get_button_text():
    return t.children["button"]["text"]

if __name__ == '__main__':
    thread.start_new_thread(threadmain, ())

    trigger = 0
    while 1:
        trigger += 1

        if trigger == 3:
            submit_to_tkinter(foo)

        if trigger == 5:
            submit_to_tkinter(bar, "changed")

        if trigger == 7:
            print submit_to_tkinter(get_button_text)

        sleep(1)
person codeape    schedule 29.07.2009
comment
Что ж, вы попали в самую точку, это работает... но я страдал от того, что не предоставил достаточно информации. Мое рассуждение состоит в том, что я хочу иметь возможность делать что-то с tkinter, где находится цикл while... Будучи немного новичком в SO, должен ли я принять ваш ответ и задать другой, более подробный вопрос? - person burito; 29.07.2009
comment
Привет, я обновил свой ответ предложением и примером кода, который выполняет это. Цикл while теперь вызывает несколько методов в потоке tkinter, используя очереди запросов/ответов. - person codeape; 29.07.2009
comment
Кстати, для производственного кода я предлагаю вам инкапсулировать окно Tkinter, поток и очереди в классе. Это сделано для того, чтобы избежать глобальных переменных, которые у нас теперь есть: request_queue, response_queue и t. Вам также нужна обработка ошибок вокруг callable(*args, **kwargs). - person codeape; 29.07.2009
comment
Я был бы счастлив с простым. Вы должны использовать для этого очередь, вы вышли далеко за рамки служебного долга, спасибо. - person burito; 30.07.2009