Become a MacRumors Supporter for $50/year with no ads, ability to filter front page stories, and private forums.

Childs

macrumors member
Original poster
May 28, 2010
48
4
Hello,

I started porting over my Foundation stuff over to Cocoa, and I use conventional loops that check for BOOLs at some interval after doing an NSTask, and when the BOOL changes I move out of the loop, like so:

Code:
     int loopInterval = 20;
     BOOL finished;
     NSString *appStatus;
     while (finished == NO) {
          appStatus = [self getAppStatusForAppID: appID];  
          // just assume getAppStatusForAppID: returns either "Successful" or "Processing"
	  if (appStatus) {
		if ([appStatus isEqualToString:@"Successful"] == YES) {
			NSLog(@"instance %@ finished!", appID);
			finished = YES;
		} else {
			NSLog(@"batch status is %@, waiting %d sec to get new status", appStatus, loopInterval);
			sleep(loopInterval);
		}
          } else {
              NSLog(@"FAIL");
              return 1;
          }
     }

Problem is, when I run this in my Cocoa app, the UI hangs whenever it hits sleep(loopInterval). It comes out of it, but its kinda annoying. I was looking around and maybe I can use NSThread, but that seems overkill for what I am doing. There is also NSTimer, but I guess I am confused about the options as I dont need to notify anything...I just need to sleep for a few seconds and check again. Any ideas as to what I should be doing?
 
If what you're doing (getAppStatusForAppID:) is not a lengthy process, you should just use NSTimer and set the interval to 20. NSTimer just calls a specified method every X seconds. If it is a lengthy process you should use NSThread. But a better design would be to post some type of notification when the app status changes to successful, instead of polling.
 
getAppStatusForAppID: does an NSTask which takes maybe 30 sec. It basically checks the status of an app based on ID. I have to analyze StandardOut/StandardErr from NSTask to see what the status of appID is. The time for the status to change from "Processing" to "Successful" is variable depending on what its doing, which is why I need to poll at regular intervals to see if its finished, and then repeat with a different appID.

Now, based on this, wouldn't the notification still require some polling since I am getting my status via NSTask? I'm not doing anything else, so new threads seems like overkill. I just dont want the pinwheel when I'm waiting to check again. The app in general is a stress tester, and all it does it send stuff off to be processed, waits for it to finish, then sends it again. Is there no equivilant to a simple sleep for Cocoa?
 
sleep() halts the thread which if running on the main thread which will stop the run loop from processing events. You *could* run the run loop manually (or NSApp's nextEvent...) but that's not really good to do for the main thread. You can also handle your NSTask's output asynchronously (see NSFileHandle's readInBackgroundAndNotify). But really, if you want to keep your synchronous methods, just throw it in an NSThread. That is IMO the best way to do this. Why are you assuming a thread is overkill? Just call detachNewThreadSelector:toTarget:withObject:, setup your autorelease pool, and run your code, ensuring you have locks setup for shared objects, which you may not even need.
 
I probably just dont understand but why use readInBackgroundAndNotify? I would still have to analyze the output once the NSTask finishes, so I dont see how a notification would help. I wasnt waiting for something to finish, just waiting to try again.

lol, NSThread is overkill because I dont know how to use it yet! And dammit I just learned how to use NSTask! :D I guess there is no way around it for now. I was putting it off so I could finish my project...as with all the objective C stuff you cant just implement one new class, because that class will require another class, and another. It is somewhat astonishing that there is no simple sleep/wait for Cocoa. I'll probably waste a week instead of just doing a simple sleep.

Anyways, thanks a lot for the input.
 
You really ought to learn how to use NSThread. If you can possibly put the process in a thread instead of a task, it would be much simpler to deal with, since the thread has direct access to the main app's context. You could simply complete the lengthy process with a "-performSelectorOnMainThread: and then exit the thread.
 
Yeah I know, I'll look at it today. I just wanted something up and running yesterday. The Foundation only version works fine so I guess there is no rush.
 
There is also NSTimer, but I guess I am confused about the options as I dont need to notify anything...I just need to sleep for a few seconds and check again. Any ideas as to what I should be doing?

You need to learn how to break your routines in half. Have the first half set a timer and then exit/return. The timer notification will then call the second half of your routine. You may have to save state somewhere in the interim (also left as an exercise for the student).

And instead of polling and wasting battery/CPU resources, you could just have your getAppStatusForAppID success post the notification for the second half of your routine, instead of the timer doing it blindly.
 
You need to learn how to break your routines in half. Have the first half set a timer and then exit/return. The timer notification will then call the second half of your routine. You may have to save state somewhere in the interim (also left as an exercise for the student).

And instead of polling and wasting battery/CPU resources, you could just have your getAppStatusForAppID success post the notification for the second half of your routine, instead of the timer doing it blindly.

But the routine wont know its successful until its checked every so often. The stress tester I'm writing calls NSTask (need to exec the 3rd party submission app) to submit a batch to a 3rd party batch processing queue. Then only way to know its done is by doing another NSTask (need to use 3rd party monitor app) to see if what was submitted is finished.

So I have two methods, one for submission, and one for monitoring. I'm not waiting for getAppStatusForAppID, but for the 3rd party monitor app to say its done. I dont conceptually see a way around needing a loop and an interval. Tonight I will do a rewrite to try and use NSThread and NSLock, but I'm not sure really how it will actually work, as I wont know if it applies until I know how to use them.
 
I dont conceptually see a way around needing a loop and an interval. Tonight I will do a rewrite to try and use NSThread and NSLock, but I'm not sure really how it will actually work, as I wont know if it applies until I know how to use them.

If you're OK with running on 10.6 only, then you should take a look at Grand Central Dispatch and programming with blocks; it makes life an awful lot easier.
 
hahaha...I'll save the Grand Central Dispatch headache for another day. I'd also need to run on 10.5 as well as 10.6. In thinking about it and looking at an example I should be able to just put the code in the original post in a NSThread, then use my sleep there. I just have to make some local instances global, and use NSLock while in the new thread and it should be OK.

Yeah yeah, kainjow already said to do that. :D I am interested to see how this blows up in my face.
 
It is somewhat astonishing that there is no simple sleep/wait for Cocoa.

As you have found out, you can call sleep in Cocoa. What you really meant to say was you are astonished to find out what sleep actually does ;-)

sleep puts the current thread to sleep - i.e. the thread does nothing else (skipping over issues of signals etc) until the sleep returns. That nothing includes processing events from the OS or updating the GUI. If your Cocoa program only has one thread then while it is sleeping there is nothing else in your program that can be processing events, hence the beach ball.

The solution, not just in Cocoa but even in command line apps, is to either do things asynchronously (e.g. using notifications, select or some other scheme, as appropriate for your problem) or use another thread (be that a thread or a process). Luckily Cocoa makes a number of those schemes fairly easy...but alas you (and indeed I) have to read the docs if you don't use them regularly.

HTH,

Andrew
 
Why would you need NSLock?

I spit stuff out to a NSTextView every time I start a new loop, find and ID, find a status, etc., so I just wanted to make sure nothing else is changing anything while I'm doing it. To be honest I dont know if I need it, but I saw it in the example I'm using as a guide and the description for NSLock in the Xcode Class Reference doesnt tell me much. It might actually say volumes, but at this time it doesnt tell me much.

As you have found out, you can call sleep in Cocoa. What you really meant to say was you are astonished to find out what sleep actually does ;-)

The solution, not just in Cocoa but even in command line apps, is to either do things asynchronously (e.g. using notifications, select or some other scheme, as appropriate for your problem) or use another thread (be that a thread or a process). Luckily Cocoa makes a number of those schemes fairly easy...but alas you (and indeed I) have to read the docs if you don't use them regularly.

HTH,

Andrew

Yup, I think I'll have to do that going forward, as my apps are all command line Foundation only which replaced what I was doing in bash, python, etc. Its such a shock that something I have done in a hour has taken 5 days and counting. I am using the Cocoa Programming for Mac OS X book and none of this stuff is discussed.

What headache? Grand Central Dispatch is there to _take away_ your headaches.

Just simply because I dont know it yet. I'm confused enough about what I already know and things I kinda know, so baby steps!
 
I spit stuff out to a NSTextView every time I start a new loop, find and ID, find a status, etc., so I just wanted to make sure nothing else is changing anything while I'm doing it. To be honest I dont know if I need it, but I saw it in the example I'm using as a guide and the description for NSLock in the Xcode Class Reference doesnt tell me much. It might actually say volumes, but at this time it doesnt tell me much.
AFAICT, an NSLock is unlikely to help you. Hard to say, really, since you didn't point to the example you're working from.

More important is the fact you can't update an NSTextView from a background thread. Fiddling with GUI elements in background threads may be crash-prone. You can message something on a thread-safe model object that triggers a change to the view, or use performSelectorOnMainThread: so all view changes occur on the main thread.
 
AFAICT, an NSLock is unlikely to help you. Hard to say, really, since you didn't point to the example you're working from.

More important is the fact you can't update an NSTextView from a background thread. Fiddling with GUI elements in background threads may be crash-prone. You can message something on a thread-safe model object that triggers a change to the view, or use performSelectorOnMainThread: so all view changes occur on the main thread.

This is what I'm using as a guide:

Code:
//  main.m
//
//  Created by Christopher Wright on 2007.06.12.

#import <Foundation/Foundation.h>

NSLock *lock;

@interface MyObject : NSObject
+(void)aMethod:(id)param;
@end

@implementation MyObject
+(void)aMethod:(id)param{
    int x;
    for(x=0;x<50;++x)
    {
        [lock lock];
        printf("Object Thread says x is %i\n",x);
        usleep(1);
        [lock unlock];
    }
}
@end

int main(int argc, char *argv[])
{
    int x;
    lock = [[NSLock alloc] init];
    [NSThread detachNewThreadSelector:@selector(aMethod:) toTarget:[MyObject class] withObject:nil];
	
    for(x=0;x<50;++x)
    {
        [lock lock];
        printf("Main thread says x is %i\n",x);
        usleep(10000);
        printf("Main thread lets go\n",x);
        [lock unlock];
        usleep(100);
    }
	
	return 0;
}

The obvious difference is he used printf. I can just make changes to my textview in the main thread after I call my method, and when it finishes, etc.
 
Skip the NSLock, just use -performSelectorOnMainThread. The waitUntilDone: parameter is quite handy: you thread can just continue on while the UI is updating: just make sure the main thread is not going to lose any important objects from draining the secondary thread's autorelease pool.
 
Register on MacRumors! This sidebar will go away, and you'll see fewer ads.