views:

243

answers:

2

I'm trying to figure out how to update an indeterminate NSProgressIndicator in the UI using a secondary thread while the main thread does some heavy lifting, just like dozens of apps do.This snippet is based on Apple's "Trivial Threads" example using Distributed Objects (DO's):

// In the main (client) thread...
- (void)doSomethingSlow:(id)sender
{ 
 [transferServer showProgress:self];

 int ctr;
 for (ctr=0; ctr <= 100; ctr++)
  {
  [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
  NSLog(@"running long task...");
  }
}

// In the secondary (server) thread...
- (oneway void)showProgress:(Controller*)controller
{
 [controller resetProgressBar];

 float ticks;
 for (ticks=0; ticks <= 100; ticks++)
  {
  [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
  [controller updateProgress:ticks];
  NSLog(@"updating progress in UI...");
  }
}

Unfortunately however, there's no way I can get both threads to run concurrently. Either the secondary thread will run and the main thread waits until it's finished OR the main thread runs followed by the secondary thread -- but not both at the same time.

Even if I pass a pointer to the server thread and ask it to update the progress bar directly (without calling the main thread back) it makes no difference. It seems that once the main thread enters a loop like this it ignores all objects sent to it. I'm still a novice with Obj-C and I'd really appreciate any help with this.

+2  A: 

You might want to try switching your threads. In general, UI updates and user input are handled on the main thread, and heavy tasks are left for secondary threads.

Martin Gordon
Hey, Martin. Dohh.. Seems I got things reversed here (as mentioned above). The reason I leaned that way was that the main thread runs through a half-dozen methods before it finishes about 10-15 secs later, so it seemed more obvious to me (a novice) to get the server thread to do the smaller job of just updating the progress bar in the meantime. Appreciate your input!
+5  A: 

AppKit isn't thread-safe, at all. You have to update the UI from the main thread, or all sorts of crazy stuff will happen (or it just won't work).

The best way is to do your work on the secondary thread, calling back to the main thread when you need to update the UI:

-(void)doSomethingSlow:(id)sender {

    [NSThread detachNewThreadSelector:@selector(threadedMethod) toTarget:self withObject:nil];

    // This will return immediately. 
}

-(void)threadedMethod {

    int ctr;
    for (ctr=0; ctr <= 100; ctr++) {
        NSLog(@"running long task...");

        [self performSelectorOnMainThread:@selector(updateUI)];
    }
}

-(void)updateUI {
    // This will be called on the main thread, and update the controls properly.
    [controller resetProgressBar];
}
iKenndac
Thanks, iKenndac. It's obvious my strategy is a bit incorrect. I thought the way to go would be to give the server thread a minor role while the main thread did the real work. As a novice, my restructuring will take a few days or more so I can't try it out right away and confirm it worked... I'll post again when I do so. Appreciate your input!
No problem. As a rule, the application's UI handling happens on the main thread, no matter what you do. Therefore, if you have work to do and want the UI to remain responsive, you need to do your work on a second thread. This doesn't hurt performance - the main thread is still a normal thread, so it'll get less priority if your second thread needs the processor time.
iKenndac