質問

I'm writing a Cocoa app that does sometimes long calculations. I'd like to be able to abort those calculations with a cmd-key sequence. I start the calculations from a command line that is implemented in a subclass of NSTextView. I've over ridden keyDown and get events there, but only when the calculation finishes. I can't figure out how to have the long calculation force an event check periodically that would cause keyDown to be called so I can set a flag to abort the calculation. Seems like there may be an easy way, but I can't seem to find it.

Without worrying about background threads, I was hoping for something like this:

//commandView is a subclass of NSTextView

commandView* theCommands;
extern int continueCalc;

@implementation commandView

- (void)keyDown:(NSEvent *)anEvent{
    static int first = 1;
    if(first){
        theCommands = self;
        first = 0;
    }
    NSString *theKey = [anEvent characters];
    [super keyDown:anEvent];
    if([theKey isEqualToString:@"s"]){
        NSLog(@"start 10 second calc");
        continueCalc = 1;
        doCalc(10);
    } else if ([theKey isEqualToString:@"x"]){
        NSLog(@"stop calc");
        continueCalc = 0;
    }
}

- (void)checkEvent{ 
    NSLog(@"would like to force an event check so that typing x would stop the calc");
}
@end

// and the separate calculation code in another file:
int continueCalc = 1;
extern commandView* theCommands;

void doCalc(int n){
    clock_t start;
    for (int i=0; i<n && continueCalc; i++) {
        start = clock();
        while ( (clock()- start)*60/CLOCKS_PER_SEC < 60);   //wait a second
        // something here to check an event
        [theCommands checkEvent];
    }
}
役に立ちましたか?

解決

One way to do this properly is with NSThread. You can very easily create a new thread which just calls your calculation method. In your calculation method, you check a variable to see if it's been aborted. Meanwhile on the main thread, the UI remains responsive and you can use an event (a key press or button press) to set the variable which is checked on the other thread. I might look something like this:

- (void)startCalculations
{
    [NSThread detachNewThreadSelector:@selector (runCalculations)
                             toTarget:myObject
                           withObject:nil]; // Or you can send an object if you need to
}

Then in the code for myObject:

- (void)runCalculations
{
    for (int i = 0; (i < maxWhatever) && (!stopCalculations); i++)
    {
        ... do one iteration of your calculation
    }
}

Then when your UI code gets the proper key or button press, you simply tell myObject to set stopCalculations to YES.

[myObject setStopCalculations:YES];

I should note that there are other ways to do this, like using GCD or pthreads directly, but this is a very simple way to just run a single method on another thread, and all the details of starting and tearing down the thread are taken care of for you.

他のヒント

The answer above works perfectly and showed me how to approach threads, which I hadn't used before. Thanks for that. With that working, I wanted the calculation to be able to report progress back to the command decoder (NSTextView). Doing that in the new thread caused problems, so I looked into using GCD. The implementation below seems to work, but this is my first GCD code and I would welcome any suggestions or caveats.

// CommandView is a subclass of NSTextView
commandView* theCommands;
extern int continueCalc;

@implementation commandView

- (void)keyDown:(NSEvent *)anEvent{
    static int first = 1;
    if(first){
        theCommands = self;
    }
    NSString *theKey = [anEvent characters];
    if([theKey isEqualToString:@"s"]){
        [self appendText:@"Start\n"];
        [self startCalculations];
        return;
    } else if ([theKey isEqualToString:@"x"]){
        [self appendText:@"\nStop"];
        continueCalc = 0;
        return;
    }
    [super keyDown:anEvent];
}

- (void)startCalculations
{
    void doCalc(int);
    continueCalc = 1;
    dispatch_queue_t queue = dispatch_queue_create("oma.oma2.CommandTask",0);
    dispatch_async(queue,^{
        for (int i=0; i<10 && continueCalc; i++) {
            doCalc(0);
            NSLog(@"%d",i);
        }
    });    
}

-(void) appendText:(NSString *) string{
    [self.textStorage.mutableString appendString:string];
}
@end

// the separate calculation code in a different file

#import "commandView.h"
extern commandView* theCommands;
int continueCalc = 1;

void doCalc(int n){
    clock_t start;
    start = clock();
    while ( (clock()- start)*60/CLOCKS_PER_SEC < 60);   //wait a second
    // print progress in the main thread
    dispatch_sync(dispatch_get_main_queue(),^{[theCommands appendText:@"."];});
}
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top