[英]Asynchronous NSStream I/O with GCD
我正在使用從中接收數據的外部設備。 我想在一個線程中異步處理其數據讀/寫隊列。
我基本上可以正常工作:有一個類可以簡單地管理兩個流,使用NSStreamDelegate
響應傳入的數據,以及響應NSStreamEventHasSpaceAvailable來發送在較早發送失敗后在緩沖區中等待的數據。
此類稱為SerialIOStream
,它不知道線程或GCD隊列。 相反,它的用戶稱為DeviceCommunicator
,它使用一個GCD隊列在其中初始化SerialIOStream
類(該類依次創建和打開流,並在當前運行循環中對其進行調度):
ioQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
dispatch_async(ioQueue, ^{
ioStreams = [[SerialIOStream alloc] initWithPath:[@"/dev/tty.mydevice"]];
[[NSRunLoop currentRunLoop] run];
});
這樣, SerialIOStream
的stream:handleEvent:
方法顯然在該GCD隊列中運行。
但是,這引起一些問題。 我相信我會遇到並發問題,甚至崩潰,主要是在將待處理數據饋送到輸出流時。 代碼中有一個關鍵部分,我將緩沖的輸出數據傳遞到流,然后查看流中實際接受了多少數據,然后從緩沖區中刪除該部分:
NSInteger n = self.dataToWrite.length;
if (n > 0 && stream.hasSpaceAvailable) {
NSInteger bytesWritten = [stream write:self.dataToWrite.bytes maxLength:n];
if (bytesWritten > 0) {
[self.dataToWrite replaceBytesInRange:NSMakeRange(0, bytesWritten) withBytes:NULL length:0];
}
}
上面的代碼可以從兩個地方調用:
DeviceCommunicator
) stream:handleEvent:
方法。 它們可能(肯定是)在單獨的線程中運行,因此我需要確保它們不會同時運行此代碼。
我以為我可以通過在發送新數據時在DeviceCommunicator
使用以下代碼來解決此問題:
dispatch_async (ioQueue, ^{
[ioStreams writeData:data];
});
( writeData
將數據添加到dataToWrite
,請參見上文,然后運行以上代碼將其發送到流。)
但是,這不起作用,顯然是因為ioQueue是並發隊列,它可能決定使用任何可用線程,因此當DeviceCommunicator
調用writeData
時導致爭用條件,同時stream:handleEvent:
也對其進行了調用stream:handleEvent:
,在單獨的線程上。
因此,我想我在對GCD隊列的明顯誤解中混入了對線程的期望(我對此更加熟悉了)。
我該如何正確解決呢?
我可以添加一個NSLock,用它來保護writeData方法,並且我相信那可以解決那個問題。 但是我不太確定應該使用GCD的方式-我覺得那會很混亂。
我是否應該使用自己的串行隊列來創建一個單獨的類,以訪問和修改dataToWrite
緩沖區?
我仍在嘗試掌握與此有關的模式。 不知何故,它看起來像是經典的生產者/消費者模式,但是在兩個層面上,我沒有做到這一點。
長話短說:不要越過溪流! (哈哈)
NSStream
是基於RunLoop的抽象(也就是說,它打算在NSRunLoop
上協同工作, NSRunLoop
是GCD之前的一種方法)。 如果您主要在其余代碼中使用GCD支持並發,那么NSStream
並不是進行I / O的理想選擇。 GCD提供了自己的用於管理I / O的API。 請參閱本頁上標題為“管理調度I / O”的部分。
如果要繼續使用NSStream
,可以通過在主線程RunLoop上調度NSStream
進行,也可以啟動專用的后台線程,在那兒的RunLoop上調度它,然后在該線程和您的GCD隊列。 (...但不要那樣做;只需硬着頭皮並使用dispatch_io
。)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.