關於wxpython多線程研究包括(import Publisher等錯誤研究)


作為一個自動化測試人員,開發基本的應用桌面程序是必須的!最近在研究wxpython相關知識,目前看到多線程一塊,發現官方文檔介紹說:"在線程中不能修改修改窗口屬性!",但是實際情況是:最近在做一個翻牆的簡單APP。我開了2個線程一個線程用於顯示設置進度(用的是第三方host,所以要下載host再覆蓋本地host) ,一個線程處理下載任務,發現第一個線程中動態的設置self.gauge(value)可以生效,並沒用到wx.CallAfter!! 需要注意的是,wxpython一次只能處理一個事件,避免同時在線程中啟用用wx.CallAfter,這樣wxpython還是一個一個去執行CallAfter傳遞函數。即:啟用N個線程,N-1個用來干事情(不要在線程種使用CallAfter),第N個線程調用CallAfter通知主線程更新界面。上面說過了本人嘗試了在線程中直接更改主窗口控件(不知道是否出現問題)。是可行的!但是,還是建議按照官方說法利用wx.CallAfter,以免發生異常崩潰。

wxpython多線程應用的常景:對於一些復雜任務的處理(比如下載若干文件),如果把這些代碼全部放在主線程中,等你觸發了這個事件后,應用程序會卡死,而且也觸發不了其他時間,幾乎處於一個假死狀態(雖然他還活着...),這樣的程序別說給別人用了,自己用都會崩潰....,所以就用到多線程,觸發了下載事件后,在線程中完成任務,主線程干干嘛干嘛,但是要給一下提示給窗口程序,比如觸發下載按鈕時,主程序有"下載中..."等字樣,下載完成后,線程通知窗口程序更新狀態為"下載完成...",這樣的交互至少是友善的。

wxpython多線程的使用方法:wxpython開發者建議的是使用wx.CallAfter+PubSub。CallAfter負者推送時間給主程序,PubSub實現wxPython應用程序與其他線程進行通信。

其實在wx.CallAfter中直接傳一個主線程的方法,更簡單!但是既然官方這樣說,我們就這樣用!!好吧我們來一個官方的例子,來看看wxpython如何使用的~

import time 
import wx 
from threading import Thread 
from wx.lib.pubsub import Publisher 
    
######################################################################## 
class TestThread(Thread): 
  """Test Worker Thread Class."""
    
  #---------------------------------------------------------------------- 
  def __init__(self): 
    """Init Worker Thread Class."""
    Thread.__init__(self) 
    self.start()  # start the thread 
    
  #---------------------------------------------------------------------- 
  def run(self): 
    """Run Worker Thread."""
    # This is the code executing in the new thread. 
    for i in range(6): 
      time.sleep(10) 
      wx.CallAfter(self.postTime, i) 
    time.sleep(5) 
    wx.CallAfter(Publisher().sendMessage, "update", "Thread finished!") 
    
  #---------------------------------------------------------------------- 
  def postTime(self, amt): 
    """
    Send time to GUI
    """
    amtOfTime = (amt + 1) * 10
    Publisher().sendMessage("update", amtOfTime) 
    
######################################################################## 
class MyForm(wx.Frame): 
    
  #---------------------------------------------------------------------- 
  def __init__(self): 
    wx.Frame.__init__(self, None, wx.ID_ANY, "Tutorial") 
    
    # Add a panel so it looks the correct on all platforms 
    panel = wx.Panel(self, wx.ID_ANY) 
    self.displayLbl = wx.StaticText(panel, label="Amount of time since thread started goes here") 
    self.btn = btn = wx.Button(panel, label="Start Thread") 
    
    btn.Bind(wx.EVT_BUTTON, self.onButton) 
    
    sizer = wx.BoxSizer(wx.VERTICAL) 
    sizer.Add(self.displayLbl, 0, wx.ALL|wx.CENTER, 5) 
    sizer.Add(btn, 0, wx.ALL|wx.CENTER, 5) 
    panel.SetSizer(sizer) 
    
    # create a pubsub receiver 
    Publisher().subscribe(self.updateDisplay, "update") 
    
  #---------------------------------------------------------------------- 
  def onButton(self, event): 
    """
    Runs the thread
    """
    TestThread() 
    self.displayLbl.SetLabel("Thread started!") 
    btn = event.GetEventObject() 
    btn.Disable() 
    
  #---------------------------------------------------------------------- 
  def updateDisplay(self, msg): 
    """
    Receives data from thread and updates the display
    """
    t = msg.data 
    if isinstance(t, int): 
      self.displayLbl.SetLabel("Time since thread started: %s seconds" % t) 
    else: 
      self.displayLbl.SetLabel("%s" % t) 
      self.btn.Enable() 
    
#---------------------------------------------------------------------- 
# Run the program 
if __name__ == "__main__": 
  app = wx.PySimpleApp() 
  frame = MyForm().Show() 
  app.MainLoop()

上述代碼我們隨便搜索下wxpython多線程的應用多會舉官方這個例子,可能是版本的問題(本人用的是wx-2.8),第一句就給來個錯,網上search一下提出這個Publisher這個moudle不存在的問題是普遍的,但是沒有相應的說法,現在就針對這個經典的wxpython程序剖析一下。這段代碼本身是簡單的,由於版本的問題可能出現如下問題。

1.ImportError: cannot import name Publisher

出現這個錯誤是正常的,我們進入wx.lib.pubsub這個模塊發現並沒有Publisher這個類,但是我們在wx.lib.pubsub這個模塊下面的pub模塊發現了Publisher的影子,原來在2.8版本時已經將這個類私有化了,見126行,_publisher = _Publisher(),同時將Publisher的subscribe與sendMessage復制給了subscribesendMessage變量見(128,131)行。所以我們這樣引入頭:from wx.lib.pubsub import pub,同時將所有的Publisher()改為pub。
2.TypeError: sendMessage() takes exactly 2 arguments (3 given)

經過1的修改,以為大功告成,運行依然報錯崩潰。出現這個問題大多數人如果硬找問題原因很難找的,我們debug進出,發現他實例化的是"C:\Python27\Lib\site-packages\wx-2.8-msw-unicode\wx\lib\pubsub\core\kwargs\publisher.py"這個模塊下的Publisher類,仔細一看sendMessage方法果然第二個參數應該傳個字典類型的所有我們將本程序24,32,71行改為wx.CallAfter(pub.sendMessage, "update", msg="Thread finished!"),pub.sendMessage("update", msg=amtOfTime),t = msg。運行一下沒問題~~注意sendMessage的鍵值(這里是msg)與subscribe監聽方法種的接收參數(這里是msg)要相同,看源碼其實就是msgKwargs這個字典參數來傳遞值的。

其實,這段代碼沒有問題的。我們注意到”C:\Python27\Lib\site-packages\wx-2.8-msw-unicode\wx\lib\pubsub\core\__init__.py" 的43行,其實core這個模塊對於Publisher這個類的加載是動態的。我們進入policies.py這個模塊第10行,發現msgDataProtocol = 'kwargs',原來如此...我們知道core模塊下面有2個包一個是arg1,一個是kwargs,我們觀察這2個包publisher模塊下Publisher類的sendMessage方法是不一樣的。原來我們這端代碼用的是arg1下的這個Publisher類,好吧,我們將policies.py的第10行改為msgDataProtocol ='arg1',還原代碼運行正確!!kwargsarg1不同點是kwargs可以傳遞多個參數給主程序(**kwargs),而arg1只能是傳遞一個參數。

經過以上簡單的論述,我們知道了wxpython是通過wx.CallAfter給主程序推入事件,通過PubSub與主程序傳遞數據。關於wxpython多線程的簡單理解就是這樣了,希望能提供幫助。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM