Multithreading in iOS - how to force a thread to wait for a condition?

14,640

Solution 1

You could also consider an NSConditionLock.

So it'd be something like this on thread 1:

[conditionLock lockWhenCondition:kConditionOkayToProceed];
[conditionLock unlockWithCondition:kConditionGettingResults];

[HUD show...]

[conditionLock lockWhenCondition:kConditionResultsFetched];
[conditionLock unlockWithCondition:kConditionOkayToProceed];

And in the HUD:

- (void)show...
{
    [conditionLock lockWhenCondition:kConditionGettingResults];

    // stuff here

    [conditionLock unlockWithCondition:kConditionResultsFetched];
}

Though a much better solution would be to pass a block or a target/selector to the HUD that it is to perform when results are fetched.

EDIT: so you'd end up with code like:

[HUD showWhileExecuting:@selector(getResults)
     onTarget:self
     withObject:nil
     animated:YES
     performWhenFinished:
     ^{
         if(self.thereAreEvents) {
             [self performSegueWithIdentifier:@"searchResults" sender:self];
         } else {
             UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"No results" message:@"Sorry, there are no results for your search. Please try again." delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil, nil];
             [alert show];
             [alert release];
         }
      }];

And in the HUD:

- (void)showWhile... performWhenFinished:(dispatch_block_t)block
{
     // all the other stuff you were going to do here, then
     // eventually...

     // if no guarantees, maybe just:
     block();

     // otherwise, if promised to dispatch to the main queue:
     dispatch_async(dispatch_get_main_queue(), block);
}

With HUD having the extra intelligence to take a dispatch_block_t as a final argument and to call it when results are in (whether guaranteeing dispatch back to the main thread or otherwise).

Solution 2

You can use a busy wait loop for a quick and dirty solution:

__block BOOL finished = NO;
dispatch_async(/* global queue */, ^{
    // …
    finished = YES;
});
while (!finished) /* waiting */;

In “real” code it’s better to use a semaphore:

dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_async(/* global queue */, ^{
    // …
    dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_release(sempahore);

This is better than the busy loop because the blocked thread does not eat CPU time.

The best solution is to keep from blocking and redesign your code to work asynchronously. In your case, you should display a spinner and start downloading data. When the data is finished downloading, you should receive an asynchronous callback (either through a block or a target/action callback) and display the results or show the error alert. Blocking with a busy loop or a semaphore is a poor man’s solution in this case.

Solution 3

This is your way: Concurrency Programming Guide

Also: Synchronization

More sharp: Using Locks. I think the last one could give the best help.

Another simple approach, it's bad but works

NSAssert(![NSThread isMainThread], @"Do not run on MAIN thread");
while (yourCondition) { [NSThread sleepForTimeInterval:0.2]; }
Share:
14,640
KerrM
Author by

KerrM

I enjoy developing engaging web and mobile applications and in my free time you will likely find me working on new projects for smartphones and tablets.

Updated on June 11, 2022

Comments

  • KerrM
    KerrM almost 2 years

    I'm creating an application that fetches a set of results from a database - I use MBProgressHUD to show the progress of the query with an animation. The method I use calls the animation while executing a method in another thread and once it's done, it hides the animation. My question is, after calling:

    [HUD showWhileExecuting:@selector(getResults) onTarget:self withObject:nil animated:YES];
    

    I would like to, if there are no results, display an alert stating this, and if there are, load the next view. So far, I have this code:

    [HUD showWhileExecuting:@selector(getResults) onTarget:self withObject:nil animated:YES];
    
    if(self.thereAreEvents) {
        [self performSegueWithIdentifier:@"searchResults" sender:self];
    } else {
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"No results" message:@"Sorry, there are no results for your search. Please try again." delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil, nil];
        [alert show];
        [alert release];
    }
    

    self.thereAreEvents gets set at the end of the getResults method. However, since that method gets called in another thread this line of execution continues and shows the alert, even though there are events in the database.

    So, from here, I have two questions: What is the easiest way to implement a wait-signal mechanism in iOS and what is the most efficient way to implement this sort of mechanism in iOS?

    Thanks!

  • KerrM
    KerrM about 12 years
    I see. There's still a lot I have to learn about programming for iOS! I'll have a look into that. Thanks!
  • KerrM
    KerrM about 12 years
    Thanks for the reply. In all honesty, I'm completely lost when it comes to programming iOS but I'll look into the asynchronous callbacks. Thanks again!
  • Tommy
    Tommy about 12 years
    The 'pass a block' pattern is relatively new, but seems to be the direction Apple are headed in per the new APIs in iOS 5. Check out e.g. TWRequest or ACAccountStore. In fact, it'd probably be better to use completionHandler: rather than performWhenFinished: to supply the block, as per the convention that seems to be emerging.
  • Jitendra Kulkarni
    Jitendra Kulkarni about 8 years
    Just a comment, dispatch_release is not needed since iOS6