2016-04-01 17 views
0

我有一个应用程序通过蓝牙与ExternalAccessory进行通信,响应中有一些延迟,所以我希望IO发生在后台线程上。如何在NSOperationQueue上使用NSRunLoop?

我设置一个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 
}]; 

此块队列的运行循环将永远不会退出,但我不知道如何正确管理运行循环,以便我可以成功从附件流中读取数据。

+0

“如果因为要获取通知或类似的API承诺而运行循环,那么您需要小心,不能仅仅消除该循环的运行并让事情继续工作。同样,不要将所有线程代码移动到NSOperation中(运行循环完好无损地运行)并将其放入操作队列中;让NSOperation块运行运行循环并不是NSOperationQueues的明智之举。“ -https://lists.apple.com/archives/cocoa-dev/2009/Sep/msg01145.html – alfwatt

+0

示例:https://horseshoe7.wordpress.com/2015/04/29/nsoperation-and-nsrunloop-结婚的必要/ – alfwatt

回答

0

任何线程可以在需要时为它创造了一个NSRunLoop,该main线程任何可可或AppKit的应用程序的默认有一个运行和任何辅助线程必须以编程方式运行它们。如果你产生了一个NSThread,线程体将负责启动NSRunLoop,但是NSOperationQueue创建它自己的一个或多个线程并向它们分派操作。

当使用其中期望一个NSRunLoop交付事件并从后台线程的API,无论是你自己的创作,或libdispatch创造了一个,你有责任确保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

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

在上面的例子中所述的皱纹是放BOOL某处,该代理可以将它设置为NO当操作完成。

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

+0

这不是使用100%CPU吗? –

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 
+0

这是从NSThread服务NSRunLoop的正确答案(和一个写得很好的答案),但我特别在系列NSOperationQueues中询问NSOperations。 – alfwatt

相关问题