iosgrand-central-dispatchnsoperationqueue

how to do blocking `dispatch_sync` using `NSOperationQueue`


Sometimes I need to do synchronous return. In dispatch_sync it's just:

__block int foo
dispatch_sync({
  foo = 3
});
return foo;

I am not sure how that translates to NSOperationQueue. I have checked the maxConcurrentOperationCount = 1, but I don't think that's blocking. My understanding is that this only makes the operation queue "serial", but not "synchronous".


Solution

  • One can use addoperations:waitUntilFinished: (addOperations(_:waitUntilFinished:) in Swift), passing true for that second parameter.


    For the sake of future readers, while one can wait using waitUntilFinished, 9 times out of 10, it is a mistake. If you have got some asynchronous operation, it is generally asynchronous for a good reason, and you should embrace asynchronous patterns and use asynchronous block completion handler parameters in your own code, rather than forcing it to finish synchronously.

    E.g., rather than:

    - (int)bar {
        __block int foo;
    
        NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
            foo = 3;
        }];
    
        [self.queue addOperations:@[operation] waitUntilFinished:true];
    
        return foo;
    }
    

    One generally would do:

    - (void)barWithCompletion:(void (^ _Nonnull)(int))completion {
        [self.queue addOperationWithBlock:^{
            completion(3);
        }];
    }
    
    [self barWithCompletion:^(int value) {
        // use `value` here
    }];
    
    // but not here, as the above runs asynchronously
    

    When developers first encounter asynchronous patterns, they almost always try to fight them, making them behave synchronously, but it is almost always a mistake. Embrace asynchronous patterns, don't fight them.

    FWIW, the new async-await patterns in Swift allow us to write asynchronous code that is far easier to reason about than the traditional asynchronous blocks/closures. So if Swift and its new concurrency patterns are an option, you might consider that as well.


    In the comments below, you seem to be concerned about the performance characteristics of synchronous behavior of operation queues versus dispatch queues. One does not use operation queues over dispatch queues for performance reasons, but rather if you benefit from the added features (including dependencies, priorities, controlling degree of concurrency, elegant cancelation features, wrapping asynchronous processes in operations, etc.).

    If performance is of optimal concern (e.g., for thread-safe synchronizing), something like an os_unfair_lock will eclipse dispatch queue performance. But we generally don't let performance dictate our choice except for those 3% of the cases where one absolutely needs that. We try to use the highest level of abstraction possible (even at the cost of modest performance hits) suitable for the task at hand.