ObjC - What is the equivalent to sleep() to use in Cocoa?

Discussion in 'Mac Programming' started by Childs, Jun 2, 2010.

  1. Childs macrumors member

    Joined:
    May 28, 2010
    #1
    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?
     
  2. kainjow Moderator emeritus

    kainjow

    Joined:
    Jun 15, 2000
    #2
    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.
     
  3. Childs thread starter macrumors member

    Joined:
    May 28, 2010
    #3
    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?
     
  4. kainjow Moderator emeritus

    kainjow

    Joined:
    Jun 15, 2000
    #4
    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.
     
  5. Childs thread starter macrumors member

    Joined:
    May 28, 2010
    #5
    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.
     
  6. Sydde macrumors 68020

    Sydde

    Joined:
    Aug 17, 2009
    #6
    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.
     
  7. Childs thread starter macrumors member

    Joined:
    May 28, 2010
    #7
    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.
     
  8. firewood macrumors 604

    Joined:
    Jul 29, 2003
    Location:
    Silicon Valley
    #8
    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.
     
  9. Childs thread starter macrumors member

    Joined:
    May 28, 2010
    #9
    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.
     
  10. gnasher729 macrumors P6

    gnasher729

    Joined:
    Nov 25, 2005
    #10
    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.
     
  11. Childs thread starter macrumors member

    Joined:
    May 28, 2010
    #11
    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.
     
  12. chown33 macrumors 604

    Joined:
    Aug 9, 2009
  13. cqexbesd macrumors regular

    Joined:
    Jun 4, 2009
    #13
    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
     
  14. gnasher729 macrumors P6

    gnasher729

    Joined:
    Nov 25, 2005
    #14
    What headache? Grand Central Dispatch is there to _take away_ your headaches.
     
  15. Childs thread starter macrumors member

    Joined:
    May 28, 2010
    #15
    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.

    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.

    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!
     
  16. chown33 macrumors 604

    Joined:
    Aug 9, 2009
    #16
    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.
     
  17. Childs thread starter macrumors member

    Joined:
    May 28, 2010
    #17
    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.
     
  18. Sydde macrumors 68020

    Sydde

    Joined:
    Aug 17, 2009
    #18
    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.
     

Share This Page