How to wait for a thread to finish in Objective-C
Asked Answered
A

7

15

I'm trying to use a method from a class I downloaded somewhere. The method executes in the background while program execution continues. I do not want to allow program execution to continue until this method finishes. How do I do that?

Amatruda answered 7/10, 2010 at 5:48 Comment(0)
G
41

Here's another way to do it using GCD:



- (void)main
{
    [self doStuffInGCD];
}

- (void)doStuffInGCD
{
    dispatch_group_t d_group = dispatch_group_create();
    dispatch_queue_t bg_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_group_async(d_group, bg_queue, ^{
        [self doSomething:@"a"];
    });

    dispatch_group_async(d_group, bg_queue, ^{
        [self doSomething:@"b"];
    });

    dispatch_group_async(d_group, bg_queue, ^{
        [self doSomething:@"c"];
    });
    
    
    // you can do this to synchronously wait on the current thread:
    dispatch_group_wait(d_group, DISPATCH_TIME_FOREVER);
    dispatch_release(d_group);
    NSLog(@"All background tasks are done!!");

    
    // ****  OR  ****
    
    // this if you just want something to happen after those are all done:
    dispatch_group_notify(d_group, dispatch_get_main_queue(), ^{
        dispatch_release(d_group);
        NSLog(@"All background tasks are done!!");        
    });
}

- (void)doSomething:(id)arg
{
    // do whatever you want with the arg here 
}
Genteel answered 22/11, 2012 at 23:52 Comment(3)
Cheers! This method is even betterAdolpho
@node ninja Would you mind selecting an answer for your question?Genteel
Cool! In my recent interview, they asked me the same concept! Thanks for saving my lifeBessel
G
9

Use an NSOperationQueue, like this
(from memory so forgive any minor errors -- you'll get the basic idea):


// ivars
NSOperationQueue *opQueue = [[NSOperationQueue alloc] init];
// count can be anything you like
[opQueue setMaxConcurrentOperationCount:5];

- (void)main
{
    [self doStuffInOperations];
}

// method
- (void)doStuffInOperations
{
    // do parallel task A
    [opQueue addOperation:[[[NSInvocationOperation alloc] initWithTarget:self selector:@selector(doSomething:) object:@"a"] autorelease]];

    // do parallel task B
    [opQueue addOperation:[[[NSInvocationOperation alloc] initWithTarget:self selector:@selector(doSomething:) object:@"b"] autorelease]];

    // do parallel task C
    [opQueue addOperation:[[[NSInvocationOperation alloc] initWithTarget:self selector:@selector(doSomething:) object:@"c"] autorelease]];


    [opQueue waitUntilAllOperationsHaveFinished];

    // now, do stuff that requires A, B, and C to be finished, and they should be finished much faster because they are in parallel.
}

- (void)doSomething:(id)arg
{
    // do whatever you want with the arg here 
    // (which is in the background, 
    // because all NSOperations added to NSOperationQueues are.)
}


Genteel answered 15/5, 2012 at 6:38 Comment(3)
I know I didn't ask this question but this was what I was looking for with my app cause I was having race conditions and I knew they were because I could click faster then the code could finish thus a partial load condition on the views. This solved my problem big time. So thank you sir, I feel like this should be marked as the true answer because it is not using a work around and is incorporating thread safe coding with the ios library.Scherzo
Second what Rob said, this is exactly what I was looking for too!Adolpho
@Adolpho - check out the other answer I posted today. It's a much cleaner way to do it and GCD is supported for all iOS 4 and up. One notable benefit of this way is that you will not get crashes when objects are deallocated out from under you because GCD automatically retains and releases all object you reference.Genteel
S
6

My first inclination is to not do what you are suggesting. The technique I've used before is to give the thread a selector to a method in the originating object (which is on the main thread). When the second thread is started, the main thread keeps executing, but puts up a busy indicator of some sort on the display. This allows user interaction to continue if required.

When the second thread ends, just before it shuts down, it calls the selector on the main thread. The method that the selector references then removes the busy indicator from the display and tells the main thread to update, picking up whatever data the second thread has generated.

I've used this successfully for an app which accesses a web service (on the second thread) and then updates the display once data is returned without locking it. This makes the user experience much nicer.

Scatology answered 7/10, 2010 at 6:2 Comment(0)
M
5

For such cases I usually using NSCondition class.

//this method executes in main thread/queue
- (void)waitForJob
{
    id __weak selfWeak = self;
    NSCondition *waitHandle = [NSCondition new];
    [waitHandle lock];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
        [selfWeak doSomethingLongtime];
        [waitHandle signal];
    });
    //waiting for background thread finished
    [waitHandle waitUntilDate:[NSDate dateWithTimeIntervalSinceNow:60]];
}
Maddux answered 21/3, 2014 at 7:13 Comment(0)
B
1

Several technical ways to synchronize multi-threads, such as NSConditionLock(mutex-lock), NSCondition(semaphore)。But they are common programming knowledge for other languages(java...) besides objective-c. I prefer to introduce run loop(special in Cocoa) to implement thread join:

NSThread *A; //global
A = [[NSThread alloc] initWithTarget:self selector:@selector(runA) object:nil]; //create thread A
[A start];

- (void)runA    
{
  [NSThread detachNewThreadSelector:@selector(runB) toTarget:self withObject:nil]; //create thread B    
  while (1)    
  {    
    if ([[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]) //join here, waiting for thread B    
    {    
      NSLog(@"thread B quit...");    
      break;    
    }    
  }    
}

- (void)runB    
{    
  sleep(1);    
  [self performSelector:@selector(setData) onThread:A withObject:nil waitUntilDone:YES modes:@[NSDefaultRunLoopMode]];    
}
Belda answered 18/8, 2013 at 8:26 Comment(0)
A
0

I'd suggest wrapping up call to the class method in your own method, and set a boolean once it completes. For eg:

BOOL isThreadRunning = NO;
- (void)beginThread {   
    isThreadRunning = YES;

    [self performSelectorInBackground:@selector(backgroundThread) withObject:nil];
}
- (void)backgroundThread {
    [myClass doLongTask];

    // Done!
    isThreadRunning = NO;
}
- (void)waitForThread {
    if (! isThreadRunning) {
        // Thread completed
        [self doSomething];
    }
}

How you wish to handle waiting is up to you: Perhaps polling with [NSThread sleepForTimeInterval:1] or similar, or sending self a message each run loop.

Ansel answered 7/10, 2010 at 6:2 Comment(4)
I did this, and it works, but it also prevents the HUD from being displayed.Amatruda
You could use NSTimer to poll and keep UI updates working correctly. It all depends on what you need. For a simple task and then update Derek's answer would be neater and less troublesome.Ansel
For a game engine or something more realtime a timer may already be in place anyway. Then sending off a thread and forgetting about it might be more what you need.Ansel
I'd suggest to use NSThread since it already has isFinished method so you don't need extra flag for that. Also you can use NSCondition (wait, signal methods) so one thread can wait, and background thread would signal when it's close to finish job, so you don't need to re-check what happens every second. If you need to do something when background task finished, then use one more background thread and wait inside of it, in this case you won't block your main thread execution.Basilicata
P
0

My technique is to check if the task can run (if the background thread is finished), and if it can't run then I use GCD to try again after a delay:

- (void)doSomething
{
  if (/* is the other thread done yet? */) {
    double delayInSeconds = 2.0;
    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
    dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
      [self doSomething];
    });
    return;
  }

  // do something
}
Propulsion answered 15/8, 2013 at 22:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.