简体   繁体   中英

Asynchronous NSStream I/O with GCD

I am working with an external device that I receive data from. I want to handle its data read/write queue asynchronously, in a thread.

I've got it mostly working: There is a class that simply manages the two streams, using the NSStreamDelegate to respond to incoming data, as well as responding to NSStreamEventHasSpaceAvailable for sending out data that's waiting in a buffer after having failed to be sent earlier.

This class, let's call it SerialIOStream , does not know about threads or GCD queues. Instead, its user, let's call it DeviceCommunicator , uses a GCD queue in which it initializes the SerialIOStream class (which in turn creates and opens the streams, scheduling them in the current runloop):

ioQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
dispatch_async(ioQueue, ^{ 
    ioStreams = [[SerialIOStream alloc] initWithPath:[@"/dev/tty.mydevice"]];
    [[NSRunLoop currentRunLoop] run];
});

That way, the SerialIOStream s stream:handleEvent: method runs in that GCD queue, apparently.

However, this causes some problems. I believe I run into concurrency issues, up to getting crashes, mainly at the point of feeding pending data to the output stream. There's a critical part in the code where I pass the buffered output data to the stream, then see how much data was actually accepted into the stream, and then removing that part from my buffer:

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];
    }
}

The above code can get called from two places:

  1. From the user ( DeviceCommunicator )
  2. From the local stream:handleEvent: method, after being told that there's space in the output stream.

Those may be (well, surely are) running in separate thread, and therefore I need to make sure they do not run concurrently this code.

I thought I'd solve this by using the following code in DeviceCommunicator when sending new data out:

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

( writeData adds the data to dataToWrite , see above, and then runs the above code that sends it to the stream.)

However, that doesn't work, apparently because ioQueue is a concurrent queue, which may decide to use any available thread, and therefore lead to a race condition when writeData get called by the DeviceCommunicator while there's also a call to it from stream:handleEvent: , on separate threads.

So, I guess I am mixing expectations of threads (which I'm a bit more familiar with) into my apparent misunderstandings with GCD queues.

How do I solve this properly?

I could add an NSLock, protecting the writeData method with it, and I believe that would solve the issue in that place. But I am not so sure that that's how GCD is supposed to be used - I get the impression that'd be a cludge.

Shall I rather make a separate class, using its own serial queue, for accessing and modifying the dataToWrite buffer, perhaps?

I am still trying to grasp the patterns that are involved with this. Somehow, it looks like a classic producer / consumer pattern, but on two levels, and I'm not doing this right.

Long story, short: Don't cross the streams! (haha)

NSStream is a RunLoop-based abstraction (which is to say that it intends to do its work cooperatively on an NSRunLoop , an approach which pre-dates GCD). If you're primarily using GCD to support concurrency in the rest of your code, then NSStream is not an ideal choice for doing I/O. GCD provides its own API for managing I/O. See the section entitled "Managing Dispatch I/O" on this page .

If you want to continue to use NSStream , you can either do so by scheduling your NSStream s on the main thread RunLoop or you can start a dedicated background thread, schedule it on a RunLoop over there, and then marshal your data back and forth between that thread and your GCD queues. (...but don't do that; just bite the bullet and use dispatch_io .)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM