Cocoa touch 如何使用GCD处理NSStream运行循环调度带来的并发问题?
我遇到了以下情况:我创建了一个GCD调度队列,并在其中调度一个Cocoa touch 如何使用GCD处理NSStream运行循环调度带来的并发问题?,cocoa-touch,concurrency,grand-central-dispatch,nsstream,nsrunloop,Cocoa Touch,Concurrency,Grand Central Dispatch,Nsstream,Nsrunloop,我遇到了以下情况:我创建了一个GCD调度队列,并在其中调度一个NSStream到当前的nsrunlop,这是规范中要求它发出委托事件的,然后我使用[[nsrunlop currentlunloop run]运行该线程的运行循环 这会产生三种可能的情况: 创建一个串行队列,其中初始写入消息通过流发送,其他写入消息仅在有来自NSStream对象的委托回调时发送,因为尝试写入新消息时不考虑此模式(这是可取的)将失败,因为队列被运行循环锁定 创建一个并发队列,其中消息可以自由写入流,因为发送到队列的块将
NSStream
到当前的nsrunlop
,这是规范中要求它发出委托事件的,然后我使用[[nsrunlop currentlunloop run]
运行该线程的运行循环
这会产生三种可能的情况:
NSStream
对象的委托回调时发送,因为尝试写入新消息时不考虑此模式(这是可取的)将失败,因为队列被运行循环锁定NSStream
文档明确指出,不应尝试读取/写入流将其计划在其中的线程反序列化考虑到这些场景,没有一个是理想的,如何解决这些问题?正如您在文档中所指出的,当您有一个基于运行循环的API,如
NSStream
,一般的期望是,与该对象的所有交互都将发生在拥有运行循环的线程上。我不确定是否存在任何问题在使用NSStream
时,最好混合使用这两种习惯用法(GCD和run循环)
除了主队列之外,GCD没有线程关联的概念,因此除非调度NSStream
的运行循环恰好是主线程运行循环,否则没有好的方法使用dispatch\u async
来调度该线程上执行的块
冒着陈述显而易见的风险,您可能应该只使用标准方法来调度其他线程上的方法。-performSelector:onThread:withObject:waituntldone:modes:
是最明显的。如果您的困惑是想处理块,那么知道堆分配的块可以像Objecti一样处理会有帮助ve-C对象并实现-invoke
选择器,就像NSInvocation
s一样。与您的问题相关的一个简单示例可能如下所示:
@interface AppDelegate ()
{
NSThread* bgthread;
}
@end
@implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
// Basic loop to get the background thread to run until you call -cancel on it
dispatch_block_t threadMain = [^{
NSThread* thread = [NSThread currentThread];
NSParameterAssert(![thread isMainThread]);
NSRunLoop* currentRunLoop = [NSRunLoop currentRunLoop];
NSPort* port = [NSPort port];
// If we dont register a mach port with the run loop, it will just exit immediately
[currentRunLoop addPort: port forMode: NSRunLoopCommonModes];
// Loop until the thread is cancelled.
while (!thread.cancelled)
{
[currentRunLoop runMode: NSDefaultRunLoopMode beforeDate: [NSDate distantFuture]];
}
[currentRunLoop removePort: port forMode: NSRunLoopCommonModes];
[port invalidate];
port = nil;
} copy];
// Start the thread
bgthread = [[NSThread alloc] initWithTarget: threadMain selector: @selector(invoke) object: nil];
[bgthread start];
// Fetch the runloop, so you can schedule an NSStream on it...
__block NSRunLoop* runloopForStream = nil;
dispatch_block_t getrunloop = [^{
runloopForStream = [NSRunLoop currentRunLoop];
} copy];
// Dispatch synchronously, so that runloopForStream is populated before we continue...
[getrunloop performSelector: @selector(invoke) onThread: bgthread withObject: nil waitUntilDone: YES];
// Schedule your stream, etc.
NSOutputStream* mystream = ...; // Your code here...
[mystream scheduleInRunLoop: runloopForStream forMode: NSDefaultRunLoopMode];
// Then later, when you want to write some data...
NSData* dataToWrite = [NSMutableData dataWithLength: 100];
dispatch_block_t doWrite = [^{
[mystream write: dataToWrite.bytes maxLength: dataToWrite.length];
} copy];
// Dispatch asynchronously to thread
[doWrite performSelector: @selector(invoke) onThread: bgthread withObject: nil waitUntilDone: NO];
}
@end
请注意,块的
-copy
是将它们复制到堆中所必需的,否则当声明方法超出范围时,它们将被解除分配。延迟到参与方,但您可以直接使用
void CFReadStreamSetDispatchQueue(CFReadStreamRef stream, dispatch_queue_t q);
void CFWriteStreamSetDispatchQueue(CFWriteStreamRef stream, dispatch_queue_t q);
其中,CFReadStreamRef可以采用桥接NSInputStream,CFWriteStreamRef可以采用桥接NSOutputStream。这样,您根本不必计划或不计划运行循环,您的流将在后台运行
来自以下内容的片段:
在Swift中,您可以直接调用以下函数:
CFReadStreamSetDispatchQueue(inputStream, streamQueue)
CFWriteStreamSetDispatchQueue(outputStream, streamQueue)
CFReadStreamSetDispatchQueue(inputStream, streamQueue)
CFWriteStreamSetDispatchQueue(outputStream, streamQueue)