如何处理在NSOperation中主线程返回的API

时间:2014-03-21 20:03:11

标签: multithreading cocoa core-data concurrency nsoperation

我需要使用MKMapSnapshotter API拍摄mapView的快照,并对它返回的图像进行一些操作,以及获取和写入核心数据。由于这是一项工作,我将它包装在NSOperation对象中,该对象在背景NSOperationQueue上执行。

MKSnapshotter api在主线程上运行完成块:

- (void)startWithCompletionHandler:(MKMapSnapshotCompletionHandler)completionHandler

或者它允许您指定要使用的调度队列:

- (void)startWithQueue:(dispatch_queue_t)queue completionHandler:(MKMapSnapshotCompletionHandler)completionHandler

...所以当它在setSnapshot:中返回时,我总是在一个不同于我应该的线程上,这可能会使我的核心数据管理对象上下文变得困难。有没有办法引用我称之为快照的线程?或者我需要在这里重新考虑我的设计吗?

- (instancetype) initWithManagedObject:(NSManagedObject *)managedObject {

    // store object id for later use
    _objectID = managedObject.objectID;
    _mainThreadContext = managedObject.managedObjectContext;
    ...
}

- (void)start {
    // do stuff with core data on the background moc
    self.backgroundContext = [[NSManagedObjectContext alloc] init];
    [self.backgroundContext setParentContext:self.parentContext];
    NSError *objectIDRetrievalError = nil;
    self.objectInBackgroundContext = [self.backgroundContext existingObjectWithID:self.objectID error:&objectIDRetrievalError];
    ...

    // take the map snapshot
    ...
    [snapshotter startWithCompletionHandler:^(MKMapSnapshot *snapshot, NSError *error) {
        ...
        self.snapshot = snapshot;
    }];
}
- (void)setSnapshot:(MKMapSnapshot *)snapshot {     
    // HELP, I'm on the wrong thread! 

    // do some expensive image stuff
    ...
    // do some more stuff with core data on the background moc
    [self.objectInBackgroundContext doStuff];
    ...
    [self.backgroundContext save:&error];
    ...
    [self finish];
}

3 个答案:

答案 0 :(得分:0)

如果您只想在主线程上调用setSnapshot,可以通过将操作提交到与主线程关联的队列来完成此操作:[NSOperationQueue mainQueue]

NSOperation *op = [NSBlockOperation blockOperationWithBlock:^{self.snapshot = snapshot;}];
[[NSOperationQueue mainQueue] addOperation:op]

GCD

的详细版本
dispatch_async(dispatch_get_main_queue(), ^{
    self.snapshot = snapshot;
});

更新v2

您可能会发现有用的coredata的内置并发支持。您可以从Concurrency Support for Managed Object Contexts

开始
- (void)start {
    // do stuff with core data on the background moc
    self.backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    [self.backgroundContext setParentContext:self.parentContext];
    __block NSError *objectIDRetrievalError = nil;
    [self.backgroundContext performBlockAndWait:^{
        self.objectInBackgroundContext = [self.backgroundContext existingObjectWithID:self.objectID error:&objectIDRetrievalError];
    }];
    ...

    // take the map snapshot
    ...
    [snapshotter startWithCompletionHandler:^(MKMapSnapshot *snapshot, NSError *error) {
        ...
        self.snapshot = snapshot;
    }];
}
- (void)setSnapshot:(MKMapSnapshot *)snapshot {     
    // HELP, I'm on the wrong thread! 

    // do some expensive image stuff
    ...
    [self.backgroundContext performBlockAndWait:^{
        // do some more stuff with core data on the background moc
        [self.objectInBackgroundContext doStuff];
        ...
        [self.backgroundContext save:&error];
    }];
    ...
    [self finish];
}

更新v3

由于@CouchDeveloper指出当前的操作实现不太正确。根据{{​​3}}和Concurrency Programming Guide引用,start方法应该

  1. 检查操作是否已取消
  2. 为任务建立执行环境(基本上创建线程或队列)
  3. 如果main已实施
    1. 从(2)
    2. 调用上下文方法main
    3. else在(2)
    4. 的上下文中在start内运行操作
  4. 然后,结合所有的东西:

    - (void)start {
        // Always check for cancellation before launching the task.
        if ([self isCancelled])
        {
            // Must move the operation to the finished state if it is canceled.
            [self willChangeValueForKey:@"isFinished"];
            finished = YES;
            [self didChangeValueForKey:@"isFinished"];
            return;
        }
        // If the operation is not canceled, begin executing the task.
        [self willChangeValueForKey:@"isExecuting"];
        // Run main in concurrent queue
        self.dispatchQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        dispatch_async(self.dispatchQueue, ^{
            [self main]
        });
        executing = YES;
        [self didChangeValueForKey:@"isExecuting"];
    }
    
    - (void)main {
        // it runs inside self.dispatchQueue queue
        // create moc with its own private queue
        self.backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
        [self.backgroundContext setParentContext:self.parentContext];
        __block NSError *objectIDRetrievalError = nil;
        [self.backgroundContext performBlockAndWait:^{
            self.objectInBackgroundContext = [self.backgroundContext existingObjectWithID:self.objectID error:&objectIDRetrievalError];
    
        }];
        ...
        // take the map snapshot
        ...
        [snapshotter startWithQueue:self.dispatchQueue // make the completion handler run in self.dispatchQueue
                  completionHandler:^(MKMapSnapshot *snapshot, NSError *error) {
                      ...
                      self.snapshot = snapshot;
                  }];
    }
    - (void)setSnapshot:(MKMapSnapshot *)snapshot {
        // will be performed in self.dispatchQueue too
        // do some more stuff with core data on the background moc
        [self.backgroundContext performBlockAndWait:^{
            [self.objectInBackgroundContext doStuff];
            ...
            [self.backgroundContext save:&error];
    
        }];
        ...
        [self finish];
    }
    

答案 1 :(得分:0)

您可以使用调度信号量等待完成处理程序运行,然后在原始上下文中完成后分配snapshot属性。在操作中阻塞线程并不好,但考虑到API的限制,它可能是最好的选择。

- (void)start {
    // do stuff with core data on the background moc
    self.backgroundContext = [[NSManagedObjectContext alloc] init];
    [self.backgroundContext setParentContext:self.parentContext];
    NSError *objectIDRetrievalError = nil;
    self.objectInBackgroundContext = [self.backgroundContext existingObjectWithID:self.objectID error:&objectIDRetrievalError];
    ...

    // take the map snapshot
    ...
    dispatch_semaphore_t sem = dispatch_semaphore_create(0);
    __block MKMapSnapshot *result;
    [snapshotter startWithCompletionHandler:^(MKMapSnapshot *snapshot, NSError *error) {
        ...
        result = snapshot;
        dispatch_semaphore_signal(sem);
    }];
    dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
    self.snapshot = result;
}

答案 2 :(得分:0)

如果您正在执行NSOperation 并发,正如您的代码所示,您基本上无法控制使用哪个线程来执行操作start方法

这使后者变得复杂。

因此,我建议使用NSPrivateQueueConcurrencyType并发类型创建 backgroundContext ,如下所示:

self.backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];

(见NSManagedObjectContext: Concurrency)。

这使您可以在托管对象上下文中执行适当的执行上下文中的任何代码":

异步调用它:

[self.backgroundContext performBlock: ^{
    // executing on the background context's private queue
    ...
}];

同步调用它:

[self.backgroundContext performBlockAndWait: ^{
    // executing on the background context's private queue
    ...
}];

然后,在您的start方法中,您可以调用"快照键盘"如下:

...
[snapshotter startWithQueue:dispatch_get_global_queue(0, 0) 
          completionHandler:^(MKMapSnapshot *snapshot, NSError *error) {
    [self.backgroundContext performBlock:^{
        // executing on the background context's private queue
        ...
        self.snapshot = snapshot;

        // here you likely need to orderly "terminate" the operation queue:
        dispatch_async(_syncQueue, ^{
            // set operation result:
            if (error) { ... }
            [self terminate];
        }
    }];
}];

旁注:

terminate的实施可能如下所示:

- (void) terminate {
    self.isExecuting = NO;
    self.isFinished = YES;
    completion_block_t completionHandler = _completionHandler;
    _completionHandler = nil;
    id result = _result;
    _self = nil;
    if (completionHandler) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            completionHandler(result);
        });
    }
}