2016-04-01 35 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

相關問題