使用线程后宏录制回放变慢?Tkinter+pynput技术求助
宏录制软件回放速度异常问题
我正在开发一款宏录制软件,用于记录鼠标移动、点击和键盘输入。用Tkinter构建GUI,pynput捕获输入。GUI包含两个按钮:回放录制内容、启动/停止录制(按下录制按钮后需再次点击停止)。
因为pynput的监听循环会导致Tkinter GUI冻结,所以用线程让GUI在pynput运行时保持可用,但现在遇到的问题是:停止录制后回放时,速度只有录制时的0.5倍,怀疑是线程导致time.time()精度不足,不知道怎么解决。
期望:GUI不冻结,pynput功能正常运行。
主GUI代码(main.py)
import threading from tkinter import * from tkinter.ttk import * from macro import startRecord, stopRecord, playRec # Window Setup window = Tk() window.title("MacroRecorder") window.geometry("350x200") def startRecordingAndChangeImg(): global stopBtn recordBtn.pack_forget() stopBtn = Button(window, text="stop", command=stopRecordingAndChangeTxt) stopBtn.pack(side=RIGHT, padx=50) threading.Thread(target=startRecord).start() def stopRecordingAndChangeImg(): global recordBtn stopBtn.pack_forget() recordBtn = Button(window, text="record", command=startRecordingAndChangeTxt) recordBtn.pack(side=RIGHT, padx=50) stopRecord() # Play Button playBtn = Button(window, text="play", command=playRec) playBtn.pack(side=LEFT, padx=50) # Record Button recordBtn = Button(window, text="record", command=startRecordingAndChangeTxt) recordBtn.pack(side=RIGHT, padx=50) window.mainloop()
宏功能代码(macro.py)
from pynput import mouse, keyboard from pynput.mouse import Button from pynput.keyboard import Key import time import os import json mouseControl = mouse.Controller() keyboardControl = keyboard.Controller() def startRecord(): global start_time global mouse_listener global keyboard_listener global macroEvents macroEvents = {'events': []} start_time = time.time() mouse_listener = mouse.Listener(on_move=on_move, on_click=on_click, on_scroll=on_scroll) keyboard_listener = keyboard.Listener(on_press=on_press, on_release=on_release) print('record started') with mouse_listener, keyboard_listener: mouse_listener.join() keyboard_listener.join() def stopRecord(): mouse_listener.stop() keyboard_listener.stop() json_macroEvents = json.dumps(macroEvents, indent=4) with open(os.path.join("C:/Users/***/Desktop/autre/python project/macro-recorder/data/test.json"), "w") as macroRecord: macroRecord.write(json_macroEvents) print('record stopped') def on_move(x, y): global start_time macroEvents["events"].append({'type': 'cursorMove', 'x': x, 'y': y, 'timestamp': time.time() - start_time}) start_time = time.time() # 其他监听事件(on_click、on_scroll、on_press、on_release)采用相同逻辑,每次都会重置计时器为0 def playRec(): print('record playing') for i in range(len(macroEvents["events"])): time.sleep(macroEvents["events"][i]["timestamp"]) if macroEvents["events"][i]["type"] == "cursorMove": mouseControl.position = (macroEvents["events"][i]["x"], macroEvents["events"][i]["y"]) elif macroEvents["events"][i]["type"] == "leftClickEvent": if macroEvents["events"][i]["pressed"] == True: mouseControl.press(Button.left) else: mouseControl.release(Button.left) elif macroEvents["events"][i]["type"] == "rightClickEvent": if macroEvents["events"][i]["pressed"] == True: mouseControl.press(Button.right) else: mouseControl.release(Button.right) elif macroEvents["events"][i]["type"] == "middleClickEvent": if macroEvents["events"][i]["pressed"] == True: mouseControl.press(Button.middle) else: mouseControl.release(Button.middle) elif macroEvents["events"][i]["type"] == "scrollEvent": mouseControl.scroll(macroEvents["events"][i]["dx"], macroEvents["events"][i]["dy"]) elif macroEvents["events"][i]["type"] == "keyboardEvent": keyToPress = macroEvents["events"][i]["key"] if 'Key.' not in macroEvents["events"][i]["key"] else special_keys[macroEvents["events"][i]["key"]] if macroEvents["events"][i]["pressed"] == True: keyboardControl.press(keyToPress) else: keyboardControl.release(keyToPress)
问题原因及解决方法
核心问题
线程不会导致time.time()精度不足(该函数在Windows下精度约1ms,Linux下更高),真正的问题出在时间记录和回放的逻辑上:
- 录制时每次触发事件就重置
start_time,记录的是两次事件的间隔; - 回放时直接对每个事件sleep记录的间隔,但
time.sleep()和事件处理本身都有执行开销,这些开销累加后会导致整体回放速度变慢。
修复步骤
记录绝对时间戳
只在录制开始时初始化一次start_time,每个事件记录相对于录制开始的绝对时间差,不再每次重置计时器:def startRecord(): global start_time # ... 其他代码不变 start_time = time.time() # 仅录制开始时初始化 def on_move(x, y): global start_time elapsed = time.time() - start_time # 记录相对于录制开始的绝对时间差 macroEvents["events"].append({'type': 'cursorMove', 'x': x, 'y': y, 'timestamp': elapsed}) # 移除start_time = time.time()这行代码其他事件回调(on_click、on_scroll等)做同样修改。
精确计算回放等待时间
回放时跟踪当前已流逝的时间,计算每个事件需要等待的时长,避免累加误差:def playRec(): print('record playing') if not macroEvents["events"]: return start_play = time.time() for event in macroEvents["events"]: # 计算需要等待的时间:事件的绝对时间差 - 已播放的时间 wait_time = event["timestamp"] - (time.time() - start_play) if wait_time > 0: time.sleep(wait_time) # 执行事件操作(以下代码不变) if event["type"] == "cursorMove": mouseControl.position = (event["x"], event["y"]) elif event["type"] == "leftClickEvent": if event["pressed"]: mouseControl.press(Button.left) else: mouseControl.release(Button.left) # ... 其他事件处理逻辑补充缺失的特殊按键映射
代码中special_keys未定义,需在macro.py中添加:special_keys = { 'Key.space': Key.space, 'Key.enter': Key.enter, 'Key.backspace': Key.backspace, 'Key.shift': Key.shift, # 根据需要补充其他特殊按键 }优化线程设置
给录制线程设置daemon=True,关闭GUI时线程自动退出:threading.Thread(target=startRecord, daemon=True).start()
额外建议
- 录制鼠标移动时,可设置最小间隔(比如只记录间隔超过10ms的移动事件),减少事件数量,提升回放性能;
- 回放时也将
playRec放到线程中执行,避免GUI因sleep阻塞。
内容的提问来源于stack exchange,提问作者LOUDO
相关产品推荐
相关产品推荐

