Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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!

like image 355
KerrM Avatar asked Mar 06 '12 18:03

KerrM


People also ask

How do you make a thread wait for some time?

In between, we have also put the main thread to sleep by using TimeUnit. sleep() method. So the main thread can wait for some time and in the meantime, T1 will resume and complete its execution.

How do I make main thread wait for other threads?

The statement “Thread. currentThread(). join()”, will tell Main thread to wait for this thread(i.e. wait for itself) to die.

Can you explain how IOS supports multi threading?

Multithreading is an implementation handled by the host operating system to allow the creation and usage of n amount of threads. Its main purpose is to provide simultaneous execution of two or more parts of a program to utilize all available CPU time.


3 Answers

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.

like image 141
zoul Avatar answered Nov 07 '22 18:11

zoul


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).

like image 25
Tommy Avatar answered Nov 07 '22 17:11

Tommy


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]; }
like image 25
SVGreg Avatar answered Nov 07 '22 18:11

SVGreg