Miro
Miro

Reputation: 5337

Using semaphore to block many, then release all

I have multiple asynchronous tasks that all depend on an initial async authentication step to succeed. I'm using a semaphore to block all the secure tasks until the authentication completes. It's mostly for timing purposes, as the tasks rely on a secure token obtained at the end of authentication. The authentication involves a network request, and may take several seconds.

The difficulty in my code below seems to be that the dispatch_semaphore_signal() issued after authentication only signals that the first semaphore lock may continue. The second would continue to block. There could in future be many moew blocking tasks, all waiting on the semaphore.

I'm wondering if there is a cleaner way to go about this blocking. I believe that each waiting task could immediately issue another dispatch_semaphore_signal(), thus releasing the next task, and so on. Is there a way to release all blocking semaphores in one call?

Is there a cleaner way to do this with GCD? I'm not adept with GCD, so code snippets help, in the context of the below usage.

dispatch_semaphore_t sem = dispatch_semaphore_create(0);

// in actuality, these 3 may be any where in the app, in different classes, methods, etc
// so a completionHandler is not possible
[self authentication];      // async, could take many seconds
[self authenticatedTask1];  // async
[self authenticatedTask2];  // async

- (void) authentication {
  // async url request, assume it is configured here
  [NSURLConnection sendAsynchronousRequest:urlRequest queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error){
     // authenticate
     authenticated = TRUE;
     secure_token = @"4rjiofwefsdf"; // obtained during auth
     dispatch_semaphore_signal(sem);
   }];
}

- (void) authenticatedTask1 {
  // put on new thread, so semaphore doesn't block program
  dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
    if(!authenticated){
      // wait until authenticated
      dispatch_semaphore_wait(sem)
    }
    // continue after authenticated, using secure_token
  });
}

- (void) authenticatedTask2 {
  // put on new thread, so semaphore doesn't block program
  dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
    if(!authenticated){
      // wait until authenticated
      dispatch_semaphore_wait(sem)
    }
    // continue after authenticated, using secure_token
  });
}

Upvotes: 3

Views: 1025

Answers (3)

Avt
Avt

Reputation: 17053

It is not very elegant but you can call 'dispatch_semaphore_signal' right after 'dispatch_semaphore_wait'. It should solve the problem.

- (void)authenticatedTask1 {
  // put on new thread, so semaphore doesn't block program
  dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
    if(!authenticated){
      // wait until authenticated
      dispatch_semaphore_wait(sem);
      dispatch_semaphore_signal(sem); // !!!
    }
    // continue after authenticated, using secure_token
  });
}

Upvotes: 2

Rich
Rich

Reputation: 8202

You could pass in the methods to be executed in a block to be run in the completltion block, then you wouldn't need to use semaphores. Also you would then not need to bother with the dispatch_async waiting for the semaphore to finish:

[self authenticationWithCompletionBlock:^{
    [self authenticatedTask1];
    [self authenticatedTask2];
}];

- (void) authenticationWithCompletionBlock:(dispatch_block_t)block {
  // async url request, assume it is configured here
  [NSURLConnection sendAsynchronousRequest:urlRequest queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error){
     // authenticate
     authenticated = TRUE;
     secure_token = @"4rjiofwefsdf"; // obtained during auth
     block();
   }];
}

If the methods are in the same class, you could just call the methods directly instead of the block.

And if you need to know when both async tasks (in your case authenticatedTask1 and authenticatedTask2) are finished, then you'd need to use dispatch groups.

Upvotes: 1

Michael
Michael

Reputation: 6515

You can put the authenticated tasks into their own suspended dispatch queue, and resume the dispatch queue once the authentication succeeded.

Upvotes: 3

Related Questions