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
s
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
  • 从本地
    流: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];
    });