views:

102

answers:

3

Here's a simplified version of my code:

- (IBAction)convert:(id)sender
{
    /* these two lines are ignored */
    [textbox setStringValue:@"converting"];
    [convertButton setEnabled:NO];

     pid_t pid;
     if((pid=fork())==-1)
     {
      [log setStringValue:@"couldn't fork a new process."];
      converting = 0;
     [convertButton setEnabled:YES];
      return;
     }else if (pid==0)
     {
      //this is the child
      sleep(2);
      exit(0);
     }else{
      int status;
      waitpid(pid,&status,0);
     }
    }
}

It's a pretty basic fork() call. The problem is, the two lines at the very top (marked with a comment) are ignored...they don't seem to execute until after the forked child exits. Why?

Edit: And what can I do to fix it?

+3  A: 

Perhaps because your code must return to the main event loop before the GUI can change state? Or, a slight variation, because the thread that runs the GUI is blocked while your code is doing a kernel-level wait()?

DigitalRoss
That sounds very likely...do you know of any solutions?
Goose Bumper
The solution is to not block. The better solution is to not use `fork()` at all. Forking a GUI level process is exceedingly difficult to do correctly. Use `NSTask`; my answer provides more details as to why.
bbum
Excellent. I figured someone who knew the API would come along sooner or later...
DigitalRoss
+7  A: 

You really really really don't want to call fork() in a Cocoa application. There are about a zillion different gotchas when doing so, mostly related to how various resources like mach ports and other system binding infrastructure survives across the fork() boundary. Threads cause all kinds of hell, too.

Use NSTask instead. While it effectively does fork()/exec() internally, it does so with considerable care to make sure it is done correctly.

bbum
This was exactly it. For anyone who is interested, I've posted as an answer the code that I used.
Goose Bumper
+1  A: 

bbum got this exactly right. Here's the code I ended up using:

- (IBAction)convert:(id)sender
{
 task = [[NSTask alloc] init];
 [task setLaunchPath: @"/usr/local/bin/lame"];

 NSArray *arguments;
 arguments = [NSArray arrayWithObjects: file,outputFile, nil];
 [task setArguments: arguments];

 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(taskFinished:) name:NSTaskDidTerminateNotification object:task];

 [task launch]; 
}
- (void) taskFinished:(NSNotification *)note {
 // code here executes after process finishes 
}
Goose Bumper
Glad to have helped. FYI: You are blocking the Main Event Loop in that code; i.e. the UI won't update until the subprocess exits. No big deal if intended or if the subprocess executes for an exceedingly short period of time....
bbum
@bbum: yep, I got told that in another question :) Edited to reflect a better solution from this thread: http://stackoverflow.com/questions/1817726/ignoring-user-input-while-waiting-for-a-task-objective-c
Goose Bumper