在以非阻塞方式更新GUI tkinter时保持读取数据



我是asyncio,线程,子进程的新手,我正在尝试构建一个应用程序,从串行连续读取数据,将它们放入另一个进程/线程/asyncio函数使用的队列中,以消费它们并显示为一个更友好的GUI。

我能够使GUI非阻塞,同时继续读取数据与下面的代码。

import tkinter as tk
import time
import queue
import logging
import serial
import sys

class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.serial_text_label = tk.Label(self, text="String")
self.serial_text_label.pack()
self.serial_text = tk.Text(self, height=1, width=21)
self.serial_text.pack()
self.port = 'COM3'
self.baud = 38400
self.ser = serial.Serial(self.port, self.baud, timeout=0)
if self.ser.isOpen():
self.ser.close()
self.ser.open()
self.ser.reset_input_buffer()
self.ser.reset_output_buffer()
logging.info("created serial port")
# start the serial_text_label "ticking"
self.update_screen()
def update_screen(self):
self.serial_text.delete('1.0', tk.END)
data = ""
data_raw = self.ser.read(1)
if data_raw == b'x02':
data_raw = self.ser.read(6)
data = "02-" + str(data_raw.hex('-'))
self.ser.reset_input_buffer()
self.ser.reset_output_buffer()
self.serial_text.insert(tk.END, data)
# self.serial_text_label.configure(text=data)
# call this function again when want to refresh
self.after(500, self.update_screen)

if __name__== "__main__":
logging.basicConfig(stream=sys.stdout, level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s (%(filename)s:%(lineno)d)', )
app = SampleApp()
app.mainloop()

我的代码的唯一问题是,所有的读取和细化的数据来自串行端口内的刷新周期,更新屏幕。我想将函数分离到与GUI刷新并发工作的某种线程/子进程。

我尝试的是在class SampleApp(tk.Tk)中创建一个async def do_serial()函数,如下所示:

async def do_serial():
logging.debug("do serial")
data = ""
data_raw = ser.read(1)
if data_raw == b'x02':
data_raw = ser.read(6)
data = "02-" + str(data_raw.hex('-'))
ser.reset_input_buffer()
ser.reset_output_buffer()
# add data to queue
if data != "":
logging.debug('put:' + str(data))
incoming_serial_queue.put(data)
await asyncio.sleep(1)

update_screen函数中我调用asyncio.run(do_serial())

if not incoming_serial_queue.empty():
data = incoming_serial_queue.get()

不幸的是,它不起作用,代码甚至没有显示GUI

是否有一种方法来处理数据从串行在异步/并行的方式,而不必写入所有的功能内刷新GUI函数?

尝试在单独的线程中进行阻塞调用。在update_screen内部,您应该调用足够快,以免冻结GUI。这意味着,您不应该读取那里的输入。

import tkinter as tk
import time
import queue
import logging
import serial
import sys
import threading
from concurrent.futures import ThreadPoolExecutor

class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.serial_text_label = tk.Label(self, text="String")
self.serial_text_label.pack()
self.serial_text = tk.Text(self, height=1, width=21)
self.serial_text.pack()
self.port = 'COM3'
self.baud = 38400
self.ser = serial.Serial(self.port, self.baud, timeout=0)
if self.ser.isOpen():
self.ser.close()
self.ser.open()
self.ser.reset_input_buffer()
self.ser.reset_output_buffer()
logging.info("created serial port")
# start the serial_text_label "ticking"
self._update_scheduled = threading.Condition()
self._terminating = threading.Event()
self.update_screen()

def mainloop(self):
with ThreadPoolExecutor() as executor:
future = executor.submit(self._do_update_screen_loop)
try:
return super().mainloop()
finally:
# letting the thread to know we're done
self._terminating.set()
with self._update_scheduled:
self._update_scheduled.notify_all()
def update_screen(self):
with self._update_scheduled:
self._update_scheduled.notify_all()
self.after(500, self.update_screen)
def _do_update_screen_loop(self):
while True:
with self._update_scheduled:
self._update_scheduled.wait()
if self._terminating.is_set():
return
self._do_update_screen()
def _do_update_screen(self):            
self.serial_text.delete('1.0', tk.END)
data = ""
data_raw = self.ser.read(1)
if data_raw == b'x02':
data_raw = self.ser.read(6)
data = "02-" + str(data_raw.hex('-'))
self.ser.reset_input_buffer()
self.ser.reset_output_buffer()
self.serial_text.insert(tk.END, data)
# self.serial_text_label.configure(text=data)

if __name__== "__main__":
logging.basicConfig(stream=sys.stdout, level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s (%(filename)s:%(lineno)d)', )
app = SampleApp()

app.mainloop()

通常,您将使用root.mainloop()作为函数,但对于async,您将不得不使用其他东西,但是如果您希望窗口更新每一帧,则必须自己创建循环。async函数就是root.update()

希望有帮助!

最新更新