【问题标题】:Freeze when using tkinter + pyhook. Two event loops and multithreading使用 tkinter + pyhook 时冻结。两个事件循环和多线程
【发布时间】:2012-09-05 09:42:23
【问题描述】:

我正在 python 2.7 中编写一个工具来记录用户按下键盘或鼠标按钮的次数。点击量将显示在屏幕左上角的一个小黑框内。即使另一个应用程序处于活动状态,程序也会记录点击次数。

它工作正常,除非我将鼠标移到框上。然后鼠标冻结几秒钟,然后程序再次运行。如果我再次将鼠标移到框上,鼠标会再次冻结,但这一次程序崩溃。

我尝试注释掉 pumpMessages() 然后程序运行。这个问题看起来很像这个问题pyhook+tkinter=crash,但那里没有给出解决方案。

其他答案表明,在 python 2.6 中同时使用 wx 和 pyhook 时,dll 文件存在错误。我不知道这是否相关。

我自己的想法是,这可能与两个并行运行的事件循环有关。我已经读到 tkinter 不是线程安全的,但我看不出如何让这个程序在单个线程中运行,因为我需要同时运行 pumpmessages() 和 mainloop()。

总结一下:为什么我的程序在鼠标悬停时会卡住?

import pythoncom, pyHook, time, ctypes, sys
from Tkinter import *
from threading import Thread

print 'Welcome to APMtool. To exit the program press delete'

## Creating input hooks

#the function called when a MouseAllButtonsUp event is called
def OnMouseUpEvent(event): 
    global clicks
    clicks+=1
    updateCounter()
    return True

#the function called when a KeyUp event is called
def OnKeyUpEvent(event): 
    global clicks
    clicks+=1
    updateCounter()
    if (event.KeyID == 46):
        killProgram()
    return True


hm = pyHook.HookManager()# create a hook manager

# watch for mouseUp and keyUp events
hm.SubscribeMouseAllButtonsUp(OnMouseUpEvent)
hm.SubscribeKeyUp(OnKeyUpEvent)

clicks = 0

hm.HookMouse()# set the hook
hm.HookKeyboard()

## Creating the window
root = Tk()
label = Label(root,text='something',background='black',foreground='grey')
label.pack(pady=0) #no space around the label
root.wm_attributes("-topmost", 1) #alway the top window
root.overrideredirect(1) #removes the 'Windows 7' box around the label

## starting a new thread to run pumMessages() and mainloop() simultaniusly
def startRootThread():
    root.mainloop()

def updateCounter():
    label.configure(text=clicks)

def killProgram():
    ctypes.windll.user32.PostQuitMessage(0) # stops pumpMessages
    root.destroy() #stops the root widget
    rootThread.join()
    print 'rootThread stopped'



rootThread = Thread(target=startRootThread)
rootThread.start()

pythoncom.PumpMessages() #pump messages is a infinite loop waiting for events

print 'PumpMessages stopped'

【问题讨论】:

    标签: multithreading tkinter event-loop pyhook


    【解决方案1】:

    我已经通过多处理解决了这个问题:

    1. 主进程处理 GUI (MainThread) 和一个从第二个进程消耗消息的线程

    2. 子进程挂钩所有鼠标/键盘事件并将它们推送到主进程(通过队列对象)

    【讨论】:

    • 你是如何检查主线程中的队列的?不是屏蔽了qui吗?
    【解决方案2】:

    从Tkinter需要在主线程中运行并且不能在这个线程外调用的信息中,我找到了解决方案:

    我的问题是PumpMessagesmainLoop 都需要在主线程中运行。为了接收输入并显示带有点击量的 Tkinter 标签,我需要在运行 pumpMessages 和短暂运行 mainLoop 之间切换以更新显示。

    为了让mainLoop()自行退出,我使用了:

    after(100,root.quit()) #root is the name of the Tk()
    mainLoop()
    

    所以在 100 毫秒后 root 调用它的 quit 方法并跳出它自己的主循环

    为了摆脱pumpMessages,我首先找到了指向主线程的指针:

    mainThreadId = win32api.GetCurrentThreadId()
    

    然后我使用了一个新线程将WM_QUIT 发送到主线程(注意PostQuitMessage(0) 仅在主线程中调用时才有效):

    win32api.PostThreadMessage(mainThreadId, win32con.WM_QUIT, 0, 0)
    

    然后可以创建一个在pumpMessagesmainLoop 之间变化的while 循环,更新其间的labeltext。在两个事件循环不再同时运行之后,我就没有问题了:

    def startTimerThread():
        while True:
            win32api.PostThreadMessage(mainThreadId, win32con.WM_QUIT, 0, 0)
            time.sleep(1)
    
    mainThreadId = win32api.GetCurrentThreadId()
    timerThread = Thread(target=startTimerThread)
    timerThread.start()
    
    while programRunning:
        label.configure(text=clicks)
        root.after(100,root.quit)
        root.mainloop()
        pythoncom.PumpMessages()
    

    感谢 Bryan Oakley 提供有关 Tkinter 的信息,感谢 Boaz Yaniv 提供stop pumpMessages() from a subthread 所需的信息

    【讨论】:

      【解决方案3】:

      Tkinter 并非设计为从除主线程之外的任何线程运行。将 GUI 放在主线程中并将对 PumpMessages 的调用放在单独的线程中可能会有所帮助。虽然你必须小心,不要从另一个线程调用任何 Tkinter 函数(可能除了event_generate)。

      【讨论】:

      • 好的,可能就是这样。我已经尝试将 pumpmessages() 放在另一个线程中,但是没有用。我在某处发现 pumpmessages() 也仅设计为在主线程中运行。那么有什么关于替代 gui 的建议吗?还是其他解决方法?
      猜你喜欢
      • 2015-11-27
      • 2013-05-20
      • 1970-01-01
      • 1970-01-01
      • 2018-07-06
      • 1970-01-01
      • 1970-01-01
      • 2021-04-09
      • 1970-01-01
      相关资源
      最近更新 更多