使用NSTimer的GCD串行队列,dispatch_sync不等待NSTimer完成

时间:2015-05-12 09:44:09

标签: ios objective-c multithreading grand-central-dispatch nstimer

我目前正在尝试创建一个queueHandler,它将一个对象数组作为输入,用于在一个简单的Double机器人上执行驱动命令。我目前正在尝试使用GCD来连续执行我的功能,但是当我在队列中使用dispatch_sync时,我不会等到NSTimer运行它的过程,但将继续尝试从我的数组中的下一个对象执行命令。

我有3个函数,一个只用两个对象初始化NSMutableArray(loadCommands)并运行queueHandler,当我切换一个开关时调用它。然后queueHandler从对象中读取变量(type,timing,queueNr)以确定将执行什么类型的驱动器功能以及执行多长时间。我认为这可以在一个switch语句中完成,我认为如果应用程序可以在主线程上执行该函数会很好(这没关系!)但它应该等到NSTimer运行它的路线。我认为使用dispatch_sync封装switch case会解决这个问题,但它会立即跳转到循环中的下一个迭代,并尝试执行下一个函数,即向后驱动3秒。

当我使用数组中的单个对象测试时,命令将毫无问题地执行。我想我会以某种方式锁定主线程。也许等待@selector语句中的NSTimer函数中的函数返回值?

我只使用Objective C玩了大约10天,我很感激我能帮到这一点!

- (void)loadCommands {
    //create an objectArray and put 2 objects inside it.

    NSMutableArray *driveCommandsArray = [[NSMutableArray alloc]     initWithCapacity:4];

     //Command 1
    DRCommands *C1 = [[DRCommands alloc] init];
    C1.timing = 3;
    C1.type = 1;
    C1.queueNr = 1;
    [driveCommandsArray addObject:C1];
     //Command 2
    DRCommands *C2 = [[DRCommands alloc] init];
    C2.timing = 3;
    C2.type = 2;
    C2.queueNr = 2;
    [driveCommandsArray addObject:C2];

    //call queueHandler
    [self queueHandler:driveCommandsArray];

}

队列处理程序:

- (void)queueHandler: (NSMutableArray*) commandArray {

//Now, I'm not sure what I'm doing here, I watched a tutorial that     
//solved a vaguely similar problem and he put a dispatch_async before the 
//dispatch_sync. I can't run the dispatch_sync clause inside the case
//statement without this. 
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    NSLog(@"Inside handler!");

    unsigned long count;
    count = [commandArray count]; //retrieve length/number of objects from the array.
    unsigned long a;

     for (a = 0; a < count;) {
     //run the loop until all objects has been managed.

        DRCommands* myObj = (DRCommands*)[commandArray objectAtIndex:a];

        //create 2 serial queues.
        dispatch_queue_t myQ1;
        myQ1 = dispatch_queue_create("myQ1", NULL);
        dispatch_queue_t myQ2;
        myQ2 = dispatch_queue_create("myQ2", NULL);

        int queueID = myObj.queueNr; //retrieve place in queue (not really used yet)
        int timeID = myObj.timing; //retrieve the amount of time the command shall be run through the NSTimer
        int typeID = myObj.type; //type of command

        NSLog(@"Inside for loop!");

        if (queueID == a+1) {
            a++;

            switch (typeID) {

                    {
                case 1:
                    NSLog(@"inside case 1");
                    dispatch_sync(myQ1, ^{ //doesn't wait for NSTimer to finish, 
                                           //letting the Double drive forward for 3 seconds,
                                           //before resuming operations.

                        counter_ = timeID;
                        seconds.text = [NSString stringWithFormat:@"%d", counter_];
                        timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(jDriveForward) userInfo:nil repeats:YES];

                    });
                    break;

                }
                    {
                case 2:
                    NSLog(@"inside case 2");
                    dispatch_sync(myQ2, ^{

                        counter_ = timeID;
                        seconds.text = [NSString stringWithFormat:@"%d", counter_];
                        timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(jDriveBackward) userInfo:nil repeats:YES];
                    });
                    break;
                }
                //add more cases
                    {
                default:
                    break;
                }
            }
        }

        NSLog(@"Exited for loop, and count is %lu", a);
        }
    });
}

驱动器命令:

//Go forward X seconds.
- (void)jDriveForward {
    shouldDriveForward_ = YES; //sets a condition which is recognized by a callback function to run the device forward.
    counter_ -= 1;
    seconds.text = [NSString stringWithFormat:@"%d", counter_];

    if (counter_ <= 0) {
        [timer invalidate];
        shouldDriveForward_ = NO;
    }
}

//Go backwards X seconds.
- (void)jDriveBackward {
    shouldDriveBackward_ = YES;
    counter_ -= 1;
    seconds.text = [NSString stringWithFormat:@"%d", counter_];

    if (counter_ <= 0) {
        [timer invalidate];
        shouldDriveBackward_ = NO;
    }
}

使用

从实验API提供了驱动功能

我正在使用&#34;令牌&#34;例如&#34; shouldDriveForward _&#34;在函数driveDoubleShouldUpdate中,在NSTimer的持续时间内为TRUE。我必须在该函数内调用我的驱动方法,使机器人不要默认为空闲模式。因此,只要X持续时间为真,前进或后退的功能就会激活。

- (void)doubleDriveShouldUpdate:(DRDouble *)theDouble {

    float drive = (driveForwardButton.highlighted) ? kDRDriveDirectionForward : ((driveBackwardButton.highlighted) ? kDRDriveDirectionBackward : kDRDriveDirectionStop);
    float turn = (driveRightButton.highlighted) ? 1.0 : ((driveLeftButton.highlighted) ? -1.0 : 0.0);
    [theDouble drive:drive turn:turn];

    //below are custom functions
    //The NSTimer I'm using keep the BOOL values below TRUE for X seconds, 
    //making the robot go forward/backward through this callback  
    //method, which I must use
    if(shouldDriveForward_ == YES) {
        [theDouble variableDrive:(float)1.0 turn:(float)0.0];
    }
    if(shouldDriveBackward_ == YES) {
        [theDouble variableDrive:(float)-1.0 turn:(float)0.0];
    }

}

2 个答案:

答案 0 :(得分:1)

你在GCD和NSTimer的组合中混淆了。没有什么可说它们可以混合在一起,但是全GCD方法可能更容易让你头脑发热。我想我已经看清了你在这里尝试做什么的要点,并将可能有帮助的东西混在一起。我放了the whole project up on GitHub,但这是它的主要内容:

#import "ViewController.h"

typedef NS_ENUM(NSUInteger, DRCommandType) {
    DRCommandUnknown = 0,
    DRCommandTypeForward = 1,
    DRCommandTypeBackward = 2,
};

@interface DRCommand : NSObject
@property DRCommandType type;
@property NSTimeInterval duration;
@end

@implementation DRCommand
@end

@interface ViewController ()

@property (weak, nonatomic) IBOutlet UILabel *commandNameLabel;
@property (weak, nonatomic) IBOutlet UILabel *secondsRemainingLabel;

@property (strong, atomic) DRCommand* currentlyExecutingCommand;
@property (copy, atomic) NSNumber* currentCommandStarted;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do an initial UI update
    [self updateUI];
}

- (IBAction)loadCommands:(id)sender
{
    DRCommand *C1 = [[DRCommand alloc] init];
    C1.duration = 3.0;
    C1.type = DRCommandTypeForward;

    DRCommand *C2 = [[DRCommand alloc] init];
    C2.duration = 3.0;
    C2.type = DRCommandTypeBackward;

    [self handleCommands: @[ C1, C2 ]];
}

- (void)handleCommands: (NSArray*)commands
{
    // For safety... it could be a mutable array that the caller could continue to mutate
    commands = [commands copy];

    // This queue will do all our actual work
    dispatch_queue_t execQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
    // We'll target the main queue because it simplifies the updating of the UI
    dispatch_set_target_queue(execQueue, dispatch_get_main_queue());

    // We'll use this queue to serve commands one at a time...
    dispatch_queue_t latchQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
    // Have it target the execQueue; Not strictly necessary but codifies the relationship
    dispatch_set_target_queue(latchQueue, execQueue);

    // This timer will update our UI at 60FPS give or take, on the main thread.
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
    dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, (1.0/60.0) * NSEC_PER_SEC, (1.0/30.0) * NSEC_PER_SEC);
    dispatch_source_set_event_handler(timer, ^{ [self updateUI]; });

    // Suspend the latch queue until we're ready to go
    dispatch_suspend(latchQueue);

    // The first thing to do for this command stream is to start UI updates
    dispatch_async(latchQueue, ^{ dispatch_resume(timer); });

    // Next enqueue each command in the array
    for (DRCommand* cmd in commands)
    {
        dispatch_async(latchQueue, ^{
            // Stop the queue from processing other commands.
            dispatch_suspend(latchQueue);

            // Update the "machine state"
            self.currentlyExecutingCommand = cmd;
            self.currentCommandStarted = @([NSDate timeIntervalSinceReferenceDate]);

            // Set up the event that'll mark the end of the command.
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(cmd.duration * NSEC_PER_SEC)), execQueue, ^{
                // Clear out the machine state for the next command
                self.currentlyExecutingCommand = nil;
                self.currentCommandStarted = nil;

                // Resume the latch queue so that the next command starts
                dispatch_resume(latchQueue);
            });
        });
    }

    // After all the commands have finished, add a cleanup block to stop the timer, and
    // make sure the UI doesn't have stale text in it.
    dispatch_async(latchQueue, ^{
        dispatch_source_cancel(timer);
        [self updateUI];
    });

    // Everything is queued up, so start the command queue
    dispatch_resume(latchQueue);
}

- (void)updateUI
{
    // Make sure we only ever update the UI on the main thread.
    if (![NSThread isMainThread])
    {
        dispatch_async(dispatch_get_main_queue(), ^{ [self updateUI]; });
        return;
    }

    DRCommand* currentCmd = self.currentlyExecutingCommand;
    switch (currentCmd.type)
    {
        case DRCommandUnknown:
            self.commandNameLabel.text = @"None";
            break;
        case DRCommandTypeForward:
            self.commandNameLabel.text = @"Forward";
            break;
        case DRCommandTypeBackward:
            self.commandNameLabel.text = @"Backward";
            break;
    }

    NSNumber* startTime = self.currentCommandStarted;
    if (!startTime || !currentCmd)
    {
        self.secondsRemainingLabel.text = @"";
    }
    else
    {
        const NSTimeInterval startTimeDbl = startTime.doubleValue;
        const NSTimeInterval currentTime = [NSDate timeIntervalSinceReferenceDate];
        const NSTimeInterval duration = currentCmd.duration;
        const NSTimeInterval remaining = MAX(0, startTimeDbl + duration - currentTime);
        self.secondsRemainingLabel.text = [NSString stringWithFormat: @"%1.3g", remaining];
    }
}

@end

如果您有任何更多解释,请在评论中告诉我。

注意:这里的另一个答案是执行sleep的命令;我的方法是完全异步的。哪种方法适合您将取决于实际所执行的命令,而这些命令并未从问题中清除。

答案 1 :(得分:0)

您只需要一个序列调度队列,您将添加任务。

我首先要定义一个实现各种命令的任务类 - 如果需要,你可以进行子类化。

<强> DRCommand.h

#import <Foundation/Foundation.h>

@interface DRCommand : NSObject

@property uint duration;

-(void) dispatch;

@end

<强> DRCommand.m

#import "DRCommand.h"

@implementation DRCommand

-(void)dispatch {

    [self startCommand];

    sleep(self.duration);

    [self stopCommand];

}

-(void) startCommand {
    NSLog(@"Override this method to actually do something");
}

-(void) stopCommand {
    NSLog(@"Override this method to stop doing something");
}

@end

然后您的运行队列代码将类似于

-(void) runQueue {
    DRCommand *c1=[DRCommand new];
    c1.duration=5;
    DRCommand *c2=[DRCommand new];
    c2.duration=7;
    DRCommand *c3=[DRCommand new];
    c3.duration=3;

    NSArray *taskArray=@[c1,c2,c3];

    dispatch_queue_t queue;
    queue = dispatch_queue_create("com.example.MyQueue", NULL);

    for (DRCommand *command in taskArray) {
        dispatch_async(queue, ^{
            [command dispatch];
        });
    }

}

请注意,您将拥有DRCommand的子类,例如DRForwardCommandDRBackwardCommand等,每个子类都有适当的startCommandstopCommand方法。