2017-06-09 67 views
0

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

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

上面的代码可以从两个地方得到所谓:

  1. 从用户(DeviceCommunicator
  2. 从本地stream:handleEvent:方法,被告知有输出流中的空间之后。

这些可能(当然,是)在单独的线程中运行,因此我需要确保它们不会同时运行此代码。

我想发送出新的数据时,我想通过使用DeviceCommunicator以下代码解决这个问题:

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

writeData添加数据到dataToWrite,见上文,然后运行该发送上述代码它到流)

但是,这并不奏效,显然是因为ioQueue是一个并发队列,它可能决定使用任何可用的线程,因此当调用writeData时会导致竞争条件还有一个来自的电话,在不同的线程上。

所以,我想我混合了对线程的期望(我更熟悉一点),因为我对GCD队列中的显然存在误解。

我该如何解决这个问题?

我可以添加一个NSLock,用它保护writeData方法,我相信这样可以解决这个问题。但我不太确定这就是GCD应该如何使用 - 我觉得这是一个混乱的印象。

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

我仍然试图把握与此有关的模式。不知何故,它看起来像一个经典的制片人/消费者模式,但在两个层面上,我没有这样做。

+0

唉 - 我不知道https://stackoverflow.com/a/31317588/43615是否是我寻求的答案。 –

回答

1

长话短说:不要过河! (haha)

NSStream是一个基于RunLoop的抽象(也就是说它打算在NSRunLoop上协作工作,这是一种在GCD之前的方法)。如果您主要使用GCD来支持其他代码中的并发,那么NSStream不是执行I/O的理想选择。 GCD提供了自己的API来管理I/O。请参阅this page上标题为“管理调度I/O”的章节。

如果您想要继续使用NSStream,您可以通过在主线程RunLoop上安排NSStream来完成此操作,也可以启动专用的后台线程,将其安排在RunLoop上,然后编组您的数据在该线程和您的GCD队列之间来回切换。 (...但不这样做,只是咬紧牙关,并使用dispatch_io。)

+0

因为我在iOS中使用蓝牙通信('EASession'提供它们),所以我得到了NSStream对象,因此可能无法避免它们。我目前通过使用'@synchronized(self){...}'周围解决了我的问题,围绕关键部分,这似乎工作。不过,使用NSLocks并不比使用NSLocks更好,因为我怀疑我导致的线程上下文切换次数超过必要的。 –

+1

@ThomasTempelmann如果这对你有用,不要让我阻止你,但我有理由相信'NSStream'对象不是以这种方式使用,并且可能存在线程相关性问题,这些问题对于你作为一个消费者(即它可能在幕后使用线程本地存储)。基于NSRunLoop是一个非常强大的指标,一个类被设计为从单个线程使用,所以请记住。 – ipmcc