【问题标题】:Call method to the main thread 'from a sub thread''从子线程'调用主线程的方法
【发布时间】:2020-08-06 06:51:06
【问题描述】:

我正在制作一个与测量设备通信的数据采集程序。需要定期检查设备的状态(例如,每 0.1 秒)以查看是否已完成采集。此外,该程序必须具有“中止”方法,因为采集有时需要超过几分钟的时间。因此我需要使用多线程。

我附上了流程图和示例代码。但是我不知道如何调用主线程来执行子线程中的方法。


python 3.7.2 wxpython 4.0.6


Flow Chart

import wx
import time
from threading import Thread

class TestFrame(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, title="Test Frame")
        panel = wx.Panel(self)

        self.Btn1 = wx.Button(panel, label="Start Measurement")
        self.Btn1.Bind(wx.EVT_BUTTON, self.OnStart)
        self.Btn2 = wx.Button(panel, label="Abort Measurement")
        self.Btn2.Bind(wx.EVT_BUTTON, self.OnAbort)
        self.Btn2.Enable(False)

        self.DoneFlag = False
        self.SubThread = Thread(target=self.Check, daemon=True)

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.Btn1, 0, wx.EXPAND)
        sizer.Add(self.Btn2, 0, wx.EXPAND)
        panel.SetSizer(sizer)

    def OnStart(self, event):
        # self.N is the number of data points
        self.N = 0
        # self.N_max is the number of data points that is going to be acquired
        self.N_max = int(input("How many data points do yo want? (greater than 1) : ")) 
        self.DoneFlag = False
        self.Btn1.Enable(False)
        self.Btn2.Enable(True)
        self.Start()

    def OnAbort(self, event):
        self.DoneFlag = True

    def Start(self):
        self.SubThread.start()

    def Done(self):
        if self.DoneFlag is True:
            self.Finish()
        elif self.DoneFlag is False:
            self.Start()

    def Finish(self):
        print("Measurement done (N = {})\n".format(self.N))
        self.Btn1.Enable(True)
        self.Btn2.Enable(False)

    def Check(self):
        # In the actual program, this method communicates with a data acquisition device to check its status
        # For example,
        # "RunningStatus" is True when the device is still running (acquisition has not been done yet),
        #                 is False when the device is in idle state (acquisition has done) 
        #
        #     [Structure of the actual program]
        #     while True:
        #         RunningStatus = GetStatusFromDevice()
        #         if RunningStatus is False or self.DoneFlag is True:
        #             break
        #         else:
        #             time.sleep(0.1)
    
        # In below code, it just waits 3 seconds then assumes the acqusition is done
        t = time.time()
        time.sleep(1)
        for i in range(3):
            if self.DoneFlag is True:
                break
            print("{} sec left".format(int(5-time.time()+t)))
            time.sleep(1)

        # Proceed to the next steps after the acquisition is done.
        if self.DoneFlag is False:
            self.N += 1
            print("Data acquired (N = {})\n".format(self.N))
            if self.N == self.N_max:
                self.DoneFlag = True
        self.Done() # This method should be excuted in the main thread
        
if __name__ == "__main__":
    app = wx.App()
    frame = TestFrame()
    frame.Show()
    app.MainLoop()

【问题讨论】:

    标签: python multithreading wxpython


    【解决方案1】:

    使用 GUI 时,不建议从另一个线程调用 GUI 函数,请参阅: https://docs.wxwidgets.org/trunk/overview_thread.html

    您的一个选择是使用events 来跟踪正在发生的事情。
    例如,当某事发生或表示进度时,一个函数会创建并分派event,而另一个函数会侦听特定的event 并做出反应。
    所以,就像 pubsub 一样,但是是原生的。
    在这里,我使用一个事件来发布有关进度的信息,而另一个事件用于发布结果,但目标不同。
    它肯定不会完全适合您的方案,但应该提供足够的信息来制定您自己的解决方案。

    import time
    import wx
    from threading import Thread
    
    import wx.lib.newevent
    progress_event, EVT_PROGRESS_EVENT = wx.lib.newevent.NewEvent()
    results_event, EVT_RESULTS_EVENT = wx.lib.newevent.NewEvent()
    
    class ThreadFrame(wx.Frame):
    
        def __init__(self, title, parent=None):
            wx.Frame.__init__(self, parent=parent, title=title)
            panel = wx.Panel(self)
            self.parent = parent
            self.btn = wx.Button(panel,label='Stop Measurements', size=(200,30), pos=(10,10))
            self.btn.Bind(wx.EVT_BUTTON, self.OnExit)
            self.progress = wx.Gauge(panel,size=(240,10), pos=(10,50), range=30)
    
            #Bind to the progress event issued by the thread
            self.Bind(EVT_PROGRESS_EVENT, self.OnProgress)
            #Bind to Exit on frame close
            self.Bind(wx.EVT_CLOSE, self.OnExit)
            self.Show()
    
            self.mythread = TestThread(self)
    
        def OnProgress(self, event):
            self.progress.SetValue(event.count)
            #or for indeterminate progress
            #self.progress.Pulse()
            if event.result != 0:
                evt = results_event(result=event.result)
                #Send back result to main frame
                try:
                    wx.PostEvent(self.parent, evt)
                except:
                    pass
    
        def OnExit(self, event):
            if self.mythread.isAlive():
                self.mythread.terminate() # Shutdown the thread
                self.mythread.join() # Wait for it to finish
            self.Destroy()
    
    class TestThread(Thread):
        def __init__(self,parent_target):
            Thread.__init__(self)
            self.parent = parent_target
            self.stopthread = False
            self.start()    # start the thread
    
        def run(self):
            curr_loop = 0
            while self.stopthread == False:
                curr_loop += 1
            # Send a result every 3 seconds for test purposes
                if curr_loop < 30:
                    time.sleep(0.1)
                    evt = progress_event(count=curr_loop,result=0)
                    #Send back current count for the progress bar
                    try:
                        wx.PostEvent(self.parent, evt)
                    except: # The parent frame has probably been destroyed
                        self.terminate()
                else:
                    curr_loop = 0
                    evt = progress_event(count=curr_loop,result=time.time())
                    #Send back current count for the progress bar
                    try:
                        wx.PostEvent(self.parent, evt)
                    except: # The parent frame has probably been destroyed
                        self.terminate()
    
        def terminate(self):
            evt = progress_event(count=0,result="Measurements Ended")
            try:
                wx.PostEvent(self.parent, evt)
            except:
                pass
            self.stopthread = True
    
    class MyPanel(wx.Panel):
    
        def __init__(self, parent):
            wx.Panel.__init__(self, parent)
            self.text_count = 0
            self.thread_count = 0
            self.parent=parent
            btn = wx.Button(self, wx.ID_ANY, label='Start Measurements', size=(200,30), pos=(10,10))
            btn.Bind(wx.EVT_BUTTON, self.Thread_Frame)
            btn2 = wx.Button(self, wx.ID_ANY, label='Is the GUI still active?', size=(200,30), pos=(10,50))
            btn2.Bind(wx.EVT_BUTTON, self.AddText)
            self.txt = wx.TextCtrl(self, wx.ID_ANY, style= wx.TE_MULTILINE, pos=(10,90),size=(400,100))
            #Bind to the result event issued by the thread
            self.Bind(EVT_RESULTS_EVENT, self.OnResult)
    
        def Thread_Frame(self, event):
            self.thread_count += 1
            frame = ThreadFrame(title='Measurement Task '+str(self.thread_count), parent=self)
    
        def AddText(self,event):
            self.text_count += 1
            txt = "Gui is still active " + str(self.text_count)+"\n"
            self.txt.write(txt)
    
        def OnResult(self,event):
            txt = "Result received " + str(event.result)+"\n"
            self.txt.write(txt)
    
    class MainFrame(wx.Frame):
    
        def __init__(self):
            wx.Frame.__init__(self, None, title='Main Frame', size=(600,400))
            panel = MyPanel(self)
            self.Show()
    
    
    if __name__ == '__main__':
        app = wx.App(False)
        frame = MainFrame()
        app.MainLoop()
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多