ios - 如何在 NSOperationQueue 上使用 NSRunLoop?

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

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

最佳答案

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

However, you need to be aware that NSRunLoop is not generally thread safe, but CFRunLoop is.这意味着当您想要在 session 处理线程上运行 block 时,您需要下降到 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 作为 session 处理线程,如下所示:

@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

关于ios - 如何在 NSOperationQueue 上使用 NSRunLoop?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36364179/

相关文章:

ios - 崩溃时未打开 Jira(Jira - Fabric 集成)

ios - 如何创建模型类以及如何与 TableView 中的部分一起使用

python - 无法在 python IDE (Mac OS X) 中输入 unicode

iphone - 我可以在不通过 NSEntityDescription 的情况下创建自定义托管对象类的新实例吗?

go - 如何使用 time.After() 而不是 time.Sleep() 来获得可中断的暂停

python - Tornado 中的队列和 ProcessPoolExecutor

java - 为什么必须从同步块(synchronized block)/方法调用等待和通知?

ios - 以特定模式获取 Objective-C 中 NSString 的所有组合的方法是什么?

c++ - 用于创建/注册虚拟存储设备的 IOKit 驱动程序

ios - iPad 和双 150 GPS 蓝牙