带GCD的异步NSStream I / O.

时间:2017-06-09 20:10:42

标签: ios grand-central-dispatch nsstream

我正在使用从中接收数据的外部设备。我想在一个线程中异步处理它的数据读/写队列。

我得到它主要工作:有一个类只管理两个流,使用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];
    }
}

上面的代码可以从两个地方调用:

  1. 来自用户(DeviceCommunicator
  2. 在被告知输出流中有空格后,从本地stream:handleEvent:方法。
  3. 那些可能(确实是)在单独的线程中运行,因此我需要确保它们不会同时运行此代码。

    我想我在发送新数据时使用DeviceCommunicator中的以下代码解决了这个问题:

    dispatch_async (ioQueue, ^{
        [ioStreams writeData:data];
    });
    

    writeData将数据添加到dataToWrite,见上文,然后运行上面的代码将其发送到流中。)

    然而,这不起作用,显然是因为ioQueue是一个并发队列,它可能决定使用任何可用的线程,因此当writeData调用DeviceCommunicator时会导致竞争条件虽然还有stream:handleEvent:在不同的线程上调用它。

    所以,我想我将线程的期望(我更熟悉一点)混合到我对GCD队列的明显误解中。

    如何正确解决这个问题?

    我可以添加一个NSLock,用它来保护writeData方法,我相信这样可以解决那个地方的问题。但我不太确定GCD应该如何使用 - 我得到的印象是'它是一个淤泥。

    我宁愿使用自己的串行队列创建一个单独的类来访问和修改dataToWrite缓冲区吗?

    我仍在努力掌握与此相关的模式。不知何故,它看起来像一个典型的生产者/消费者模式,但在两个层面,我不是这样做的。

1 个答案:

答案 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。)