Ios 带GCD的异步NSStream I/O
我正在使用从中接收数据的外部设备。我希望在线程中异步处理其数据读/写队列 我已经让它大部分工作:有一个类只管理两个流,使用Ios 带GCD的异步NSStream I/O,ios,grand-central-dispatch,nsstream,Ios,Grand Central Dispatch,Nsstream,我正在使用从中接收数据的外部设备。我希望在线程中异步处理其数据读/写队列 我已经让它大部分工作:有一个类只管理两个流,使用NSStreamDelegate响应传入的数据,以及响应nsstreamventhasspace,用于发送先前发送失败后在缓冲区中等待的数据 我们称它为SerialIOStream,它不知道线程或GCD队列。相反,它的用户(我们称之为DeviceCommunicator)使用GCD队列,在该队列中初始化SerialIOStream类(该类反过来创建并打开流,并在当前运行循环中
NSStreamDelegate
响应传入的数据,以及响应nsstreamventhasspace,用于发送先前发送失败后在缓冲区中等待的数据
我们称它为SerialIOStream
,它不知道线程或GCD队列。相反,它的用户(我们称之为DeviceCommunicator
)使用GCD队列,在该队列中初始化SerialIOStream
类(该类反过来创建并打开流,并在当前运行循环中调度它们):
这样,显然,SerialIOStream
sstream: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
)流:handleEvent:
方法,在被告知输出流中有空间后DeviceCommunicator
中的以下代码来解决这个问题:
dispatch_async (ioQueue, ^{
[ioStreams writeData:data];
});
(writeData
将数据添加到dataToWrite
,请参见上文,然后运行上述代码将数据发送到流。)
然而,这显然不起作用,因为ioQueue是一个并发队列,它可能决定使用任何可用的线程,因此当DeviceCommunicator
调用writeData
时,会导致竞态条件,而在不同的线程上也会从stream:handleEvent:
调用它
所以,我想我把对线程的期望(我更熟悉一点)和对GCD队列的明显误解混为一谈了
我如何正确地解决这个问题
我可以添加一个NSLock,用它来保护writeData方法,我相信这会解决这个问题。但我不太确定GCD应该如何使用——我觉得这是一个累赘
我应该使用自己的串行队列创建一个单独的类来访问和修改dataToWrite
缓冲区吗
我仍在努力掌握与此相关的模式。不知何故,它看起来像一个经典的生产者/消费者模式,但在两个层面上,我做得不对。长话短说:不要跨流!(哈哈)
NSStream
是一种基于运行循环的抽象(也就是说,它打算在nsrunlop
上协同工作,这是一种早于GCD的方法)。如果您主要使用GCD来支持代码其余部分的并发性,那么NSStream
不是执行I/O的理想选择。GCD提供了自己的API来管理I/O。请参阅上题为“管理调度I/O”的部分
如果要继续使用
NSStream
,可以通过在主线程RunLoop上调度NSStream
来实现,也可以启动专用后台线程,在那里的RunLoop上调度,然后在该线程和GCD队列之间来回封送数据。(…但不要这样做;只要咬紧牙关,使用dispatch_io
)长话短说:不要越过河流!(哈哈)
NSStream
是一种基于运行循环的抽象(也就是说,它打算在nsrunlop
上协同工作,这是一种早于GCD的方法)。如果您主要使用GCD来支持代码其余部分的并发性,那么NSStream
不是执行I/O的理想选择。GCD提供了自己的API来管理I/O。请参阅上题为“管理调度I/O”的部分
如果要继续使用
NSStream
,可以通过在主线程RunLoop上调度NSStream
来实现,也可以启动专用后台线程,在那里的RunLoop上调度,然后在该线程和GCD队列之间来回封送数据。(…但不要这样做;只要咬紧牙关,使用dispatch\u io
)嗯-我想知道这是否是我所寻求的答案。嗯-我想知道这是否是我所寻求的答案。我在iOS中进行蓝牙通信时得到了NSStream对象(EASession
提供了它们),因此可能无法避免它们。目前,我通过在关键部分使用@synchronized(self){…}
a解决了我的问题,这似乎很有效。不过,这并不比使用NSLocks好多少,因为我怀疑我导致了更多的线程上下文切换。@ThomasTempelmann如果这对您有效,请不要让我阻止您,但我有理由相信,NSStream
对象不打算以这种方式使用,而且,作为一个消费者,您可能不太清楚线程关联性问题(即,它可能在幕后使用线程本地存储)。基于nsrunlop是一个很强的指标,表明类设计用于单个线程,因此请记住这一点。在iOS中执行蓝牙通信时,我收到了NSStream对象(EASession
提供了这些对象),因此可能无法避免它们。目前,我通过在cri周围使用@synchronized(self){…}
a解决了我的问题
dispatch_async (ioQueue, ^{
[ioStreams writeData:data];
});