2009-10-18 40 views
3

我在使用python中的io_add_watch監視器時遇到了問題(通過gobject)。我想在每次通知後對整個緩衝區進行非阻塞式讀取。下面的代碼(縮短了一下):gobject io monitoring + nonblocking reads

class SomeApp(object): 

    def __init__(self): 
     # some other init that does a lot of stderr debug writes 
     fl = fcntl.fcntl(0, fcntl.F_GETFL, 0) 
     fcntl.fcntl(0, fcntl.F_SETFL, fl | os.O_NONBLOCK) 
     print "hooked", gobject.io_add_watch(0, gobject.IO_IN | gobject.IO_PRI, self.got_message, [""]) 
     self.app = gobject.MainLoop() 

    def run(self): 
     print "ready" 
     self.app.run() 

    def got_message(self, fd, condition, data): 
     print "reading now" 
     data[0] += os.read(0, 1024) 
     print "got something", fd, condition, data 
     return True 

gobject.threads_init() 
SomeApp().run() 

這裏的竅門 - 當我不激活調試輸出運行程序,我沒有得到got_message電話。當我先寫了很多東西到stderr,問題就消失了。如果我沒有寫任何與此代碼中可見的打印件不同的東西,我不會收到stdin消息信號。另一個有趣的事情是,當我嘗試運行啓用了stderr調試的同一應用程序時,但通過strace(以檢查是否有任何錯過的fcntl/ioctl調用),問題再次出現。

所以總之:如果我先寫很多stderr沒有strace,io_watch的作品。如果我用strace寫很多,或者根本不寫,io_watch不起作用。

「其他init」部分需要一些時間,所以如果我在看到「hooked 2」輸出之前鍵入一些文本,然後在「ready」之後按下「ctrl + c」,則會調用get_message回調,但讀取調用拋出EAGAIN,所以緩衝區似乎是空的。

相關標準輸入strace的日誌:

ioctl(0, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 
ioctl(0, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 
fcntl(0, F_GETFL)      = 0xa002 (flags O_RDWR|O_ASYNC|O_LARGEFILE) 
fcntl(0, F_SETFL, O_RDWR|O_NONBLOCK|O_ASYNC|O_LARGEFILE) = 0 
fcntl(0, F_GETFL)      = 0xa802 (flags O_RDWR|O_NONBLOCK|O_ASYNC|O_LARGEFILE) 

有沒有人有什麼樣的一些想法是怎麼回事嗎?


編輯:另一個線索。我試圖重構應用程序,在不同的線程中讀取數據並通過管道傳回。它「種」的作品:

... 
     rpipe, wpipe = os.pipe() 
     stopped = threading.Event() 
     self.stdreader = threading.Thread(name = "reader", target = self.std_read_loop, args = (wpipe, stopped)) 
     self.stdreader.start() 
     new_data = "" 
     print "hooked", gobject.io_add_watch(rpipe, gobject.IO_IN | gobject.IO_PRI, self.got_message, [new_data]) 

    def std_read_loop(self, wpipe, stop_event): 
     while True: 
     try: 
      new_data = os.read(0, 1024) 
      while len(new_data) > 0: 
       l = os.write(wpipe, new_data) 
       new_data = new_data[l:] 
     except OSError, e: 
      if stop_event.isSet(): 
       break 
      time.sleep(0.1) 
... 

令人驚訝的是,如果我只是把相同的文本放在一個新的管道,一切都開始工作。問題是:

  • 第一行是不是「發現」在所有 - 我只得到第二次及以後行
  • 它的fugly

也許這將在給別人一個線索爲什麼會這樣?

回答

0

documentation說你應該從回調中返回TRUE,否則它將從事件源列表中刪除。

+0

它在真實代碼中返回True。這並不重要 - 如果它沒有被調用,它根本不會被調用 - 甚至一次。 – viraptor 2009-10-19 18:26:54

2

這聽起來像是一種競爭條件,其中有一些延遲來設置回調,否則會影響您是否可以設置回調的環境發生變化。

我會仔細看看在您致電io_add_watch()之前會發生什麼。比如Python的文檔的fcntl說:

這個模塊中的所有功能,採取 文件描述符fd爲他們的第一個 說法。這可以是整數文件 描述符,例如 sys.stdin.fileno()返回的描述符,或者是諸如sys.stdin本身的文件對象 ,其中 提供了返回 真實文件描述符的fileno()。

很明顯,當你認爲STDIN的FD == 0時,你不會這麼做。我會先改變它,然後重試。

的另一件事是,如果FD已阻塞,那麼你的過程可以等待,而其他非阻塞的進程正在運行,因此也取決於你做什麼第一的定時差。如果重構fcntl的東西,那麼在程序啓動之後很快完成,甚至在導入GTK模塊之前會發生什麼呢?

我不確定我是否理解爲什麼使用GTK GUI的程序首先要從標準輸入讀取數據。如果你實際上是試圖捕捉另一個進程的輸出,你應該使用的子模塊來建立一個管道,然後io_add_watch()在管道上,像這樣:

proc = subprocess.Popen(command, stdout = subprocess.PIPE) 
gobject.io_add_watch(proc.stdout, glib.IO_IN, self.write_to_buffer) 

同樣,在這個例子中,我們確保我們在致電io_add_watch(之前,有一個有效的打開的FD)。

通常情況下,當使用gobject.io_add_watch(),它只是gobject.MainLoop()之前調用。例如,下面是一些使用io_add_watch捕獲IO_IN的工作代碼。

+0

我需要的描述符在這種情況下保證爲0。我需要把它作爲描述符來處理/它不是一個子進程。移動fnctl並不幸運。我沒有使用gtk,只是gobject/gst集成。不過好的建議無論如何。不幸的是我的代碼仍然不起作用。 – viraptor 2009-10-26 20:14:32

+0

我不確定爲什麼你會在混合中投擲線索,因爲這會增加複雜性併爲比賽條件創造更多機會。看看這裏的片段,特別是使用GStreamer實現播放器的片段http://pyneo.org/documentation/snippets/ – 2009-10-28 20:03:48

+0

這是另一個GStreamer播放器的例子http://de.pastebin.ca/raw/ 933910 我真的認爲你需要把它簡化爲一些有用的東西,並從那裏建立起來,而不是相反。 – 2009-10-28 20:05:38

0

如果在任何標準錯誤輸出之前首先掛鉤回調會發生什麼情況?當您啓用調試輸出時它仍然會被調用嗎?

另外,我想你大概應該是在你的處理器一邊喊os.read()直到它沒有給出數據,以防> 1024個字節成爲呼叫之間準備。

您是否嘗試過使用select模塊在後臺線程模擬gio功能?那樣有用嗎?這是什麼平臺以及你處理什麼類型的FD? (file?socket?pipe?)

+0

更改初始化順序沒有幫助。我試着先沖洗描述符(同時刷新和讀取),但這並沒有幫助。我在linux上運行這個程序,而fd是一個由程序生成的管道,它產生了我的應用程序。它可以在後臺使用線程+阻塞讀取,但我已經在同一個程序中使用glib的io作爲其他fds,所以我不想爲每個輸入使用不同的方法。 – viraptor 2009-10-29 15:21:10