1

I want to call a completion handler synchronously inside a critical section (using @synchronized block). I am trying to wait for completion handler using semaphore, but the semaphore signal is never called.

Here is what I am doing:

NSNumber *lock = 0;
@synchronized(lock) {
    // critical section code begins
    dispatch_semaphore_t sema = dispatch_semaphore_create(0);
    [self someMethodWithCompletionHandler:^(BOOL result) {
        // execute completion handler
        dispatch_semaphore_signal(sema);
    }];
    // wait for completion block to finish
    dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
    // critical section code ends
}

I believe, due to @synchronized block the completion handler is called on the same thread as the caller which results in a deadlock. Is that right? If yes, how else can this be achieved?

Thanks!

coder_andy
  • 376
  • 1
  • 3
  • 17
  • it is generally a bad idea to try and make asynchronous things synchronous. You need to ensure that `someMethodWithCompletionHandler` both does its work and dispatches the completion handler on a different queue to the one you have blocked with your ’wait`. If you are waiting on the main queue then that is very bad. Adding a synchronized block just makes it more complicated as you now have a second potential hold-and-wait deadlock trigger. – Paulw11 Sep 26 '17 at 21:13
  • Rather than using an @syncrhonized block, I would dispatch the whole critical section asynchronously on a private serial dispatch queue and dispatch `someMethodWithCompletionHandler` asynchrously on another utility queue or the main queue. Then your semaphore can safely block your private queue. – Paulw11 Sep 26 '17 at 21:17
  • But I want critical section code to be synchronous.Won't dispatching whole critical section asynchronously to another serial thread make it asynchronous? I have code after the critical section that should be executed after the critical section is done. – coder_andy Sep 27 '17 at 14:27
  • @Paulw11 I tried using dispatch_sync but that executes in main thread! Any ideas? – coder_andy Sep 27 '17 at 15:12
  • Don’t use dispatch sync. Embrace the fact that it should be asynchronous. Use a dispatch group and dispatch group notify to execute the code after the critical section. Dispatch sync will execute the code on whatever queue you dispatch onto, but it will still block the current queue waiting for completion of that dispatch, which is bad if the current queue is the main queue. Even if you dispatch onto another queue, code may execute on the main thread if that thread is available. The main queue *always* uses the main thread. Other queues *may* use the main thread – Paulw11 Sep 27 '17 at 19:43
  • ok, let me clarify a bit more. I have multiple background threads passing through the critical section, so I need to synchronize them (using locks, @synchronized or serial dispatch queue) and I need to also need to make the someMethodWithCompletionHandler method synchronous and I do not have control over it. someMethodWithCompletionHandler makes http request and processes response in the completion handler. Can you give an example how to achieve this using dispatch group? – coder_andy Sep 28 '17 at 18:36
  • It doesn’t make sense to have a critical section span an asynchronous network operation. Critical sections should be as short as possible, but if you really do want to wait until the completion handler is invoked then use `dispatchGroup.wait()`. Since you will be blocking the serial queue, no other tasks can enter it, protecting your critical section and you won’t be blocking the main thread. – Paulw11 Sep 28 '17 at 20:34

1 Answers1

0

I would avoid @synchronized and use a serial dispatch queue to serialize the work in the critical section. You can still use a semaphore to block the serial dispatch queue until the asynchronous operation is complete.

The serial dispatch queue guarantees that only one block of code can enter the critical section at a time and there is no risk of blocking the main queue.

You will need to create the serial queue during your class initialisation

@property (strong,nonatomic) dispatch_queue_t serialQueue;

- (id)init {
    if (self = [super init]) {
        self.serialQueue = dispatch_queue_create("com.example.CriticalTaskQueue", NULL);
    }
    return self;
}

- submitSomeCriticalWork() {

    dispatch_async(self.serialQueue, ^{
        dispatch_semaphore_t sema = dispatch_semaphore_create(0);
        [self someMethodWithCompletionHandler:^(BOOL result) {
         // execute completion handler
            dispatch_semaphore_signal(sema);
        }];
    // wait for completion block to finish
     dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
    // critical section code ends
   }];

}
Paulw11
  • 108,386
  • 14
  • 159
  • 186