如何在NSOperationQueue上使用NSRunLoop?

时间:2016-04-01 19:14:58

标签: ios macos concurrency foundation nsrunloop

我有一个通过蓝牙与ExternalAccessory通信的应用程序,响应有一些延迟,所以我希望IO在后台线程上发生。

我为单线程操作设置了一个NSOperationQueue来排队我的请求:

self.sessionQueue = [NSOperationQueue new];
self.sessionQueue.maxConcurrentOperationCount = 1;

如果我安排对来自该队列的EAAccessory流进行读写操作,我的应用程序崩溃了,因为来自套接字的数据无法在队列所在的线程上没有NSRunLoop的情况下传递使用。在初始化队列之后,我立即创建一个空NSMachPort的运行循环以使其保持运行并启动它:

[self.sessionQueue addOperationWithBlock:^{
    NSRunLoop* queueLoop = [NSRunLoop currentRunLoop];
    [queueLoop addPort:[NSMachPort port] forMode:NSRunLoopCommonModes];
    [queueLoop run]; // <- this blocks
}];

这会阻止队列,因为运行循环永远不会退出,但我不确定如何正确管理运行循环,以便我可以成功读取辅助流。

2 个答案:

答案 0 :(得分:1)

你不应该尝试在NSOperation内运行一个运行循环。 Grand Central Dispatch拥有运行该操作的线程。您应该启动自己的线程并将其运行循环用于会话流。

However, you need to be aware that NSRunLoop is not generally thread safe, but CFRunLoop is.这意味着当您想要在会话处理线程上运行阻止时,需要下拉到CFRunLoop级别。

此外,获取对后台线程的运行循环的引用的唯一方法是在该后台线程上运行某些东西。因此,第一步是创建自己的NSThread子类,导出自己的运行循环:

typedef void (^MyThreadStartCallback)(CFRunLoopRef runLoop);

@interface MyThread: NSThread

/// After I'm started, I dispatch to the main queue to call `callback`,
// passing my runloop. Then I destroy my reference to `callback`.
- (instancetype)initWithCallback:(MyThreadStartCallback)callback;

@end

@implementation MyThread {
    MyThreadStartCallback _callback;
}

- (instancetype)initWithCallback:(MyThreadStartCallback)callback {
    if (self = [super init]) {
        _callback = callback;
    }
    return self;
}

- (void)main {
    CFRunLoopRef runLoop = CFRunLoopGetCurrent();
    dispatch_async(dispatch_get_main_queue(), ^{
        _callback(runLoop);
    });
    _callback = nil;
    CFRunLoopRun();
}

@end

现在,您可以创建MyThread的实例,并传入回调。当你启动MyThread时,它将使回调在主线程上运行,并且它将自己的(MyThread)运行循环传递给回调。因此,您可以使用MyThread作为会话处理线程,如下所示:

@implementation Thing {
    CFRunLoopRef _sessionRunLoop;
}

- (void)scheduleStreamsOfSession:(EASession *)session {
    MyThread *thread = [[MyThread alloc] initWithCallback:^(CFRunLoopRef runLoop) {
        // Here I'm on the main thread, but the session-handling thread has
        // started running and its run loop is `runLoop`.
        [self scheduleStreamsOfSession:session inRunLoop:runLoop];
    }];
    [thread start];
}

- (void)scheduleStreamsOfSession:(EASession *)session inRunLoop:(CFRunLoopRef)runLoop {

    // Here I'm on the main thread. I'll save away the session-handling run loop
    // so I can run more blocks on it later, perhaps to queue data for writing
    // to the output stream.
    _sessionRunLoop = runLoop;

    NSInputStream *inputStream = session.inputStream;
    NSOutputStream *outputStream = session.outputStream;

    // Here I'm on the main thread, where it's not safe to use the
    // session-handling thread's NSRunLoop, so I'll send a block to
    // the session-handling thread.
    CFRunLoopPerformBlock(runLoop, kCFRunLoopCommonModes, ^{

        // Here I'm on the session-handling thread, where it's safe to
        // use NSRunLoop to schedule the streams.
        NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];
        [inputStream scheduleInRunLoop:currentRunLoop forMode:NSRunLoopCommonModes];
        [outputStream scheduleInRunLoop:currentRunLoop forMode:NSRunLoopCommonModes];

    });

    // CFRunLoopPerformBlock does **not** wake up the run loop. Since I want
    // to make sure the block runs as soon as possible, I have to wake up the
    // run loop manually:
    CFRunLoopWakeUp(_sessionRunLoop);
}

@end

答案 1 :(得分:0)

如果需要,任何线程都可以为其创建NSRunLoop,任何Cocoa或AppKit应用程序的main线程都默认运行,任何辅助线程都必须以编程方式运行它们。如果你产生NSThread线程主体将负责启动NSRunLoop,但NSOperationQueue创建它自己的线程或线程并向它们发送操作。

当使用期望NSRunLoop向后台线程传递事件的API时,您自己创建的事件或libdispatch已创建的事件,您负责确保{ {1}}正在运行。通常,您需要运行循环,直到在每个NSRunLoop任务中满足某些条件,我在NSBlockOperation上写了一个类别,这简化了这一点:

NSRunLoop

在这种情况下,您可以安排一个#import <Foundation/Foundation.h> @interface NSRunLoop (Conditional) -(BOOL)runWhileCondition:(BOOL *)condition inMode:(NSString *)mode inIntervals:(NSTimeInterval) quantum; @end #pragma mark - @implementation NSRunLoop (Conditional) -(BOOL)runWhileCondition:(BOOL *)condition inMode:(NSString *)mode inIntervals:(NSTimeInterval) quantum { BOOL didRun = NO; BOOL shouldRun = YES; NSPort *dummyPort = [NSMachPort port]; [self addPort:dummyPort forMode:NSDefaultRunLoopMode]; while (shouldRun) { @autoreleasepool { didRun = [self runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:quantum]]; shouldRun = (didRun ? *condition : NO); } } [self removePort:dummyPort forMode:NSDefaultRunLoopMode]; return didRun; } @end ,它将启动运行循环并运行,直到指定的条件为NSBlockOperation

NO

上面示例中的皱纹是将BOOL置于代理可以在操作完成时将其设置为__block BOOL streamOperationInProgress = YES; [self.sessionQueue addOperationWithBlock:^{ NSRunLoop *queueLoop = [NSRunLoop currentRunLoop]; NSStream *someStream = // from somewhere... [someStream setDelegate:self]; [someStream scheduleInRunLoop:queueLoop forMode:NSDefaultRunLoopMode]: // the delegate implementation of stream:handleEvent: // sets streamOperationInProgress = NO; [queueLoop runWhileCondition:&streamOperationInProgress inMode:NSDefaultRunLoopMode inIntervals:0.001]; }]; 的位置。

以下是NSRunLoop+Condition类别的要点。