我正在使用从中接收数据的外部设备。我想在一个线程中异步处理它的数据读/写队列。
我得到它主要工作:有一个类只管理两个流,使用NSStreamDelegate
响应传入的数据,以及响应NSStreamEventHasSpaceAvailable发送在缓冲区中等待的数据在未能早点发送之后。
这个类,我们称之为SerialIOStream
,不知道线程或GCD队列。相反,它的用户,我们称之为DeviceCommunicator
,使用一个GCD队列,在其中初始化SerialIOStream
类(进而创建并打开流,在当前的runloop中调度它们):
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是一个并发队列,它可能决定使用任何可用的线程,因此当writeData
调用DeviceCommunicator
时会导致竞争条件虽然还有stream:handleEvent:
在不同的线程上调用它。
所以,我想我将线程的期望(我更熟悉一点)混合到我对GCD队列的明显误解中。
如何正确解决这个问题?
我可以添加一个NSLock,用它来保护writeData方法,我相信这样可以解决那个地方的问题。但我不太确定GCD应该如何使用 - 我得到的印象是'它是一个淤泥。
我宁愿使用自己的串行队列创建一个单独的类来访问和修改dataToWrite
缓冲区吗?
我仍在努力掌握与此相关的模式。不知何故,它看起来像一个典型的生产者/消费者模式,但在两个层面,我不是这样做的。
答案 0 :(得分:1)
长话短说:不要越过溪流! (哈哈)
NSStream
是一个基于RunLoop的抽象(也就是说它打算在NSRunLoop
上合作完成它的工作,这种方法早于GCD)。如果您主要使用GCD来支持其余代码中的并发,那么NSStream
不是执行I / O的理想选择。 GCD提供了自己的API来管理I / O.请参阅标题为"管理调度I / O&#34的部分;在this page。
如果您想继续使用NSStream
,您可以通过在主线程RunLoop上安排NSStream
来执行此操作,也可以启动专用后台线程,在RunLoop上安排它在那里,然后在该线程和您的GCD队列之间来回封送您的数据。 (...但不要这样做;只需咬紧牙关并使用dispatch_io
。)