【发布时间】:2017-11-11 22:55:11
【问题描述】:
我正在使用从其接收数据的外部设备。我想在一个线程中异步处理它的数据读/写队列。
我已经让它大部分工作了:有一个类可以简单地管理两个流,使用 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];
});
这样,SerialIOStreams 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 缓冲区,也许?
我仍在尝试掌握与此相关的模式。不知何故,它看起来像一个经典的生产者/消费者模式,但在两个层面上,我做的不对。
【问题讨论】:
-
嗯 - 我想知道 stackoverflow.com/a/31317588/43615 是否是我所寻求的答案。
标签: ios grand-central-dispatch nsstream