如何在不阻塞的情况下获取数据?
How can i get data without blocking?
我有一个串口外接设备用来获取数据。我设置了两个定时器。其中一个必须用于绘图(0.5sn),另一个用于写入文本文件(15sn)。定时器不应该通过列表或数组从彼此获取数据。因为有时我需要关闭绘图按钮。
所以我必须从同一个资源中获取数据(即将到来的连续数据),对吧?但是当我尝试这样做时,它被阻止了。
以及如何不阻塞地获取数据?
作为示例,以下代码运行时不会阻塞:
# -*- coding: utf-8 -*-
#!/usr/bin/python
import time
import numpy as np
import sys
import wx
def next_data():
t0 = time.time()
return t0
class MyFrame1 ( wx.Frame ):
def __init__( self, parent ):
wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, title = wx.EmptyString, pos = wx.DefaultPosition, size = wx.Size( 223,183 ), style = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL )
self.SetSizeHints( wx.DefaultSize, wx.DefaultSize )
bSizer1 = wx.BoxSizer( wx.VERTICAL )
self.toggleBtn1 = wx.ToggleButton( self, wx.ID_ANY, u"Btn 1(enabled)", wx.DefaultPosition, wx.DefaultSize, 0 )
bSizer1.Add( self.toggleBtn1, 1, wx.ALL|wx.EXPAND, 5 )
self.toggleBtn2 = wx.ToggleButton( self, wx.ID_ANY, u"Btn 2(enabled)", wx.DefaultPosition, wx.DefaultSize, 0 )
bSizer1.Add( self.toggleBtn2, 1, wx.ALL|wx.EXPAND, 5 )
self.SetSizer( bSizer1 )
self.Layout()
self.timer1 = wx.Timer()
self.timer1.SetOwner( self, 1 )
self.timer1.Start( 1000 )
self.timer2 = wx.Timer()
self.timer2.SetOwner( self, 2 )
self.timer2.Start( 1000 )
self.Centre( wx.BOTH )
# Connect Events
self.toggleBtn1.Bind( wx.EVT_TOGGLEBUTTON, self.btn1_f )
self.toggleBtn2.Bind( wx.EVT_TOGGLEBUTTON, self.btn2_f )
self.Bind( wx.EVT_TIMER, self.timer1_f, id=1 )
self.Bind( wx.EVT_TIMER, self.timer2_f, id=2 )
def btn1_f( self, event ):
event.Skip()
def btn2_f( self, event ):
event.Skip()
def timer1_f( self, event ):
t1 = next_data()
print("t1 : ",t1)
def timer2_f( self, event ):
t2 = next_data()
print("t2*****: ",t2)
app = wx.App()
f = MyFrame1(None)
f.Show(True)
app.MainLoop()
并打印(如预期):
t2*****: 1555568620.1363716
t1 : 1555568620.1363716
t2*****: 1555568621.1300163
t1 : 1555568621.1300163
但是我的串口不能像上面那样正常工作:
# -*- coding: utf-8 -*-
#!/usr/bin/python
import time
import numpy as np
import sys
import wx
import serial
ser = serial.Serial('COM9',9600)
def next_data():
#===========================================================================
# for line in ser:
# return line
#===========================================================================
data_str = ser.read(ser.inWaiting())
return data_str
## also the commented above 2 lines gave same output.
class MyFrame1 ( wx.Frame ):
def __init__( self, parent ):
wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, title = wx.EmptyString, pos = wx.DefaultPosition, size = wx.Size( 223,183 ), style = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL )
self.SetSizeHints( wx.DefaultSize, wx.DefaultSize )
bSizer1 = wx.BoxSizer( wx.VERTICAL )
self.toggleBtn1 = wx.ToggleButton( self, wx.ID_ANY, u"Btn 1(enabled)", wx.DefaultPosition, wx.DefaultSize, 0 )
bSizer1.Add( self.toggleBtn1, 1, wx.ALL|wx.EXPAND, 5 )
self.toggleBtn2 = wx.ToggleButton( self, wx.ID_ANY, u"Btn 2(enabled)", wx.DefaultPosition, wx.DefaultSize, 0 )
bSizer1.Add( self.toggleBtn2, 1, wx.ALL|wx.EXPAND, 5 )
self.SetSizer( bSizer1 )
self.Layout()
self.timer1 = wx.Timer()
self.timer1.SetOwner( self, 1 )
self.timer1.Start( 1000 )
self.timer2 = wx.Timer()
self.timer2.SetOwner( self, 2 )
self.timer2.Start( 1000 )
self.Centre( wx.BOTH )
# Connect Events
self.toggleBtn1.Bind( wx.EVT_TOGGLEBUTTON, self.btn1_f )
self.toggleBtn2.Bind( wx.EVT_TOGGLEBUTTON, self.btn2_f )
self.Bind( wx.EVT_TIMER, self.timer1_f, id=1 )
self.Bind( wx.EVT_TIMER, self.timer2_f, id=2 )
def btn1_f( self, event ):
event.Skip()
def btn2_f( self, event ):
event.Skip()
def timer1_f( self, event ):
t1 = next_data()
print("t1 : ",t1)
def timer2_f( self, event ):
t2 = next_data()
print("t2*****: ",t2)
app = wx.App()
f = MyFrame1(None)
f.Show(True)
app.MainLoop()
及其输出如下:
t2*****: b'\xb5b\x010\x04\x018\>$GNRMC,063337.00.....$GNGGA...
t1 : b''
t2*****: b'\xb5b\x010\x04\x01\x18\>$GNRMC,063338.00.....$GNGGA...
t1 : b''
和(注释两行):
t2*****: b'$GPGSV,3,1,11,05,43,248,30,07,31,068,12,08,15,048,23,09,16,128,30*7B\r\n'
t1 : b'$GPGSV,3,2,11,13,42,311,27,15,10,310,18,17,04,157,09,28,70,161,27*72\r\n'
可以看出,无论是t1
没有得到数据还是t1
只是得到下一个。我还设置了定时器(100 毫秒),但输出相同。有人可以指导我吗,我错过了什么?
对于 Gui / wx 的非阻塞操作python 你需要使用线程,
你可以使用 wx.lib.delayedresult
或者你可以使用 "threading" python 我更喜欢的模块
另外,如果您需要从串行端口获取文本数据,您应该将它们解码为 utf-8,示例:
ser = serial.Serial('COM9',9600)
try:
line = ser.readline()
line = line.decode("utf-8")
except UnicodeDecodeError:
line = "\ncan't decode from serial, choose different baudrate\n"
print(line) # or do whatever you want with line
这也是一个使用线程读取串行端口的示例,您可以将其用作您想要执行的操作的想法
我在代码中添加了解释注释
from threading import Thread
from time import sleep
# you can define a class inherited from Thread class or simply use a function
class ReadData(Thread):
def __init__(self):
super().__init__()
self.running = True # used as signal to kill the thread
self.ser = serial.Serial('COM9',9600) # open serial
def run(self): # run method need to be defined for threading to work
while self.running:
print('running')
sleep(1)
self.ser.close()
def read(self): # reading data from port
if ser.is_open:
try:
line = ser.readline()
line = line.decode("utf-8") # decode data
except UnicodeDecodeError:
line = "\ncan't decode from serial, choose different baudrate\n"
display(line)
line = ""
def kill(self): # terminate thread safely
self.running = False
read_data = ReadData() # define new thread object
read_data.start() # start the thread
sleep(5)
# in case you want to kill the running thread
print('killing running thread')
read_data.kill()
编辑:
要在您的线程和主 GUI 之间进行通信,即您想安全地传递串行数据,您应该避免使用全局变量并改用队列,您可以查看此answer了解如何使用它们
您不需要使用 2 个定时器来定期保存数据。而且您也不需要使用线程。只需在主框架 class 中保留一个计数器变量(或带有计时器的 class 并收集数据——您的示例很简单,可能不需要拆分)并使用它来确定何时写入数据。另外:将从串行端口读取的数据保存在同一个数组中 class 以便您可以绘制或保存它或您可能想要的任何其他内容:
在MyFrame1.__init__()
中添加
self.last_saved_time = 0
self.plotting = True
self.data = []
然后在MyFrame1.timer1()
中做
# read and save data
t1 = next_data()
self.data.append(t1) # (or do more parsing, convert to numpy arrays, etc
# send to plotting if it is enabled
if self.plotting:
self.plot_data()
# save if needed
now = time.time()
if (now - self.last_saved_time) > 15:
self.save_data_to_file()
同样,您不一定需要两个计时器或线程。您 可以 在单独的线程中进行绘图或 i/o,但是您的速度相对较慢。
最终将代码拆分为 "data collector" class 并带有自己的事件循环,然后将其传输到 GUI 框架以根据需要进行绘图可能会更明智。但是这个例子足够小,还不需要这样的重构。
我有一个串口外接设备用来获取数据。我设置了两个定时器。其中一个必须用于绘图(0.5sn),另一个用于写入文本文件(15sn)。定时器不应该通过列表或数组从彼此获取数据。因为有时我需要关闭绘图按钮。
所以我必须从同一个资源中获取数据(即将到来的连续数据),对吧?但是当我尝试这样做时,它被阻止了。
以及如何不阻塞地获取数据? 作为示例,以下代码运行时不会阻塞:
# -*- coding: utf-8 -*-
#!/usr/bin/python
import time
import numpy as np
import sys
import wx
def next_data():
t0 = time.time()
return t0
class MyFrame1 ( wx.Frame ):
def __init__( self, parent ):
wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, title = wx.EmptyString, pos = wx.DefaultPosition, size = wx.Size( 223,183 ), style = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL )
self.SetSizeHints( wx.DefaultSize, wx.DefaultSize )
bSizer1 = wx.BoxSizer( wx.VERTICAL )
self.toggleBtn1 = wx.ToggleButton( self, wx.ID_ANY, u"Btn 1(enabled)", wx.DefaultPosition, wx.DefaultSize, 0 )
bSizer1.Add( self.toggleBtn1, 1, wx.ALL|wx.EXPAND, 5 )
self.toggleBtn2 = wx.ToggleButton( self, wx.ID_ANY, u"Btn 2(enabled)", wx.DefaultPosition, wx.DefaultSize, 0 )
bSizer1.Add( self.toggleBtn2, 1, wx.ALL|wx.EXPAND, 5 )
self.SetSizer( bSizer1 )
self.Layout()
self.timer1 = wx.Timer()
self.timer1.SetOwner( self, 1 )
self.timer1.Start( 1000 )
self.timer2 = wx.Timer()
self.timer2.SetOwner( self, 2 )
self.timer2.Start( 1000 )
self.Centre( wx.BOTH )
# Connect Events
self.toggleBtn1.Bind( wx.EVT_TOGGLEBUTTON, self.btn1_f )
self.toggleBtn2.Bind( wx.EVT_TOGGLEBUTTON, self.btn2_f )
self.Bind( wx.EVT_TIMER, self.timer1_f, id=1 )
self.Bind( wx.EVT_TIMER, self.timer2_f, id=2 )
def btn1_f( self, event ):
event.Skip()
def btn2_f( self, event ):
event.Skip()
def timer1_f( self, event ):
t1 = next_data()
print("t1 : ",t1)
def timer2_f( self, event ):
t2 = next_data()
print("t2*****: ",t2)
app = wx.App()
f = MyFrame1(None)
f.Show(True)
app.MainLoop()
并打印(如预期):
t2*****: 1555568620.1363716
t1 : 1555568620.1363716
t2*****: 1555568621.1300163
t1 : 1555568621.1300163
但是我的串口不能像上面那样正常工作:
# -*- coding: utf-8 -*-
#!/usr/bin/python
import time
import numpy as np
import sys
import wx
import serial
ser = serial.Serial('COM9',9600)
def next_data():
#===========================================================================
# for line in ser:
# return line
#===========================================================================
data_str = ser.read(ser.inWaiting())
return data_str
## also the commented above 2 lines gave same output.
class MyFrame1 ( wx.Frame ):
def __init__( self, parent ):
wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, title = wx.EmptyString, pos = wx.DefaultPosition, size = wx.Size( 223,183 ), style = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL )
self.SetSizeHints( wx.DefaultSize, wx.DefaultSize )
bSizer1 = wx.BoxSizer( wx.VERTICAL )
self.toggleBtn1 = wx.ToggleButton( self, wx.ID_ANY, u"Btn 1(enabled)", wx.DefaultPosition, wx.DefaultSize, 0 )
bSizer1.Add( self.toggleBtn1, 1, wx.ALL|wx.EXPAND, 5 )
self.toggleBtn2 = wx.ToggleButton( self, wx.ID_ANY, u"Btn 2(enabled)", wx.DefaultPosition, wx.DefaultSize, 0 )
bSizer1.Add( self.toggleBtn2, 1, wx.ALL|wx.EXPAND, 5 )
self.SetSizer( bSizer1 )
self.Layout()
self.timer1 = wx.Timer()
self.timer1.SetOwner( self, 1 )
self.timer1.Start( 1000 )
self.timer2 = wx.Timer()
self.timer2.SetOwner( self, 2 )
self.timer2.Start( 1000 )
self.Centre( wx.BOTH )
# Connect Events
self.toggleBtn1.Bind( wx.EVT_TOGGLEBUTTON, self.btn1_f )
self.toggleBtn2.Bind( wx.EVT_TOGGLEBUTTON, self.btn2_f )
self.Bind( wx.EVT_TIMER, self.timer1_f, id=1 )
self.Bind( wx.EVT_TIMER, self.timer2_f, id=2 )
def btn1_f( self, event ):
event.Skip()
def btn2_f( self, event ):
event.Skip()
def timer1_f( self, event ):
t1 = next_data()
print("t1 : ",t1)
def timer2_f( self, event ):
t2 = next_data()
print("t2*****: ",t2)
app = wx.App()
f = MyFrame1(None)
f.Show(True)
app.MainLoop()
及其输出如下:
t2*****: b'\xb5b\x010\x04\x018\>$GNRMC,063337.00.....$GNGGA...
t1 : b''
t2*****: b'\xb5b\x010\x04\x01\x18\>$GNRMC,063338.00.....$GNGGA...
t1 : b''
和(注释两行):
t2*****: b'$GPGSV,3,1,11,05,43,248,30,07,31,068,12,08,15,048,23,09,16,128,30*7B\r\n'
t1 : b'$GPGSV,3,2,11,13,42,311,27,15,10,310,18,17,04,157,09,28,70,161,27*72\r\n'
可以看出,无论是t1
没有得到数据还是t1
只是得到下一个。我还设置了定时器(100 毫秒),但输出相同。有人可以指导我吗,我错过了什么?
对于 Gui / wx 的非阻塞操作python 你需要使用线程, 你可以使用 wx.lib.delayedresult
或者你可以使用 "threading" python 我更喜欢的模块
另外,如果您需要从串行端口获取文本数据,您应该将它们解码为 utf-8,示例:
ser = serial.Serial('COM9',9600)
try:
line = ser.readline()
line = line.decode("utf-8")
except UnicodeDecodeError:
line = "\ncan't decode from serial, choose different baudrate\n"
print(line) # or do whatever you want with line
这也是一个使用线程读取串行端口的示例,您可以将其用作您想要执行的操作的想法
我在代码中添加了解释注释
from threading import Thread
from time import sleep
# you can define a class inherited from Thread class or simply use a function
class ReadData(Thread):
def __init__(self):
super().__init__()
self.running = True # used as signal to kill the thread
self.ser = serial.Serial('COM9',9600) # open serial
def run(self): # run method need to be defined for threading to work
while self.running:
print('running')
sleep(1)
self.ser.close()
def read(self): # reading data from port
if ser.is_open:
try:
line = ser.readline()
line = line.decode("utf-8") # decode data
except UnicodeDecodeError:
line = "\ncan't decode from serial, choose different baudrate\n"
display(line)
line = ""
def kill(self): # terminate thread safely
self.running = False
read_data = ReadData() # define new thread object
read_data.start() # start the thread
sleep(5)
# in case you want to kill the running thread
print('killing running thread')
read_data.kill()
编辑:
要在您的线程和主 GUI 之间进行通信,即您想安全地传递串行数据,您应该避免使用全局变量并改用队列,您可以查看此answer了解如何使用它们
您不需要使用 2 个定时器来定期保存数据。而且您也不需要使用线程。只需在主框架 class 中保留一个计数器变量(或带有计时器的 class 并收集数据——您的示例很简单,可能不需要拆分)并使用它来确定何时写入数据。另外:将从串行端口读取的数据保存在同一个数组中 class 以便您可以绘制或保存它或您可能想要的任何其他内容:
在MyFrame1.__init__()
中添加
self.last_saved_time = 0
self.plotting = True
self.data = []
然后在MyFrame1.timer1()
中做
# read and save data
t1 = next_data()
self.data.append(t1) # (or do more parsing, convert to numpy arrays, etc
# send to plotting if it is enabled
if self.plotting:
self.plot_data()
# save if needed
now = time.time()
if (now - self.last_saved_time) > 15:
self.save_data_to_file()
同样,您不一定需要两个计时器或线程。您 可以 在单独的线程中进行绘图或 i/o,但是您的速度相对较慢。
最终将代码拆分为 "data collector" class 并带有自己的事件循环,然后将其传输到 GUI 框架以根据需要进行绘图可能会更明智。但是这个例子足够小,还不需要这样的重构。