ios - GCD serial queue with NSTimer, dispatch_sync doesn't wait for the NSTimer to complete -
i'm trying make queuehandler
takes object array input executing drive commands on simple double robot. i'm trying use gcd in order serially execute functions, when i'm using dispatch_sync
on queue in won't wait until nstimer
has run course, continue try , execute commands next object in array.
i have 3 functions, 1 initializes nsmutablearray
(loadcommands) 2 objects , runs queuehandler
, called when toggle switch. queuehandler
reads variables objects(type, timing, queuenr) determine type of drive function executed , how long. thought done in switch statement, , figured great if app execute function on main thread(that ok!) should wait until nstimer
has run course. thought encapsulating switch case dispatch_sync solve promptly skips next iteration in loop , tries execute next function instead, drive backwards 3 seconds.
when test single object in array command executed without trouble. suppose i'm locking main thread somehow. perhaps waiting return value function in @selector
in nstimer
statement help?
i've played objective c 10 days, i'd appreciate bit!
- (void)loadcommands { //create objectarray , 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]; }
queue handler:
- (void)queuehandler: (nsmutablearray*) commandarray { //now, i'm not sure i'm doing here, watched tutorial //solved vaguely similar problem , put dispatch_async before //dispatch_sync. can't run dispatch_sync clause inside 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 array. unsigned long a; (a = 0; < count;) { //run loop until 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 used yet) int timeid = myobj.timing; //retrieve amount of time command shall run through nstimer int typeid = myobj.type; //type of command nslog(@"inside loop!"); if (queueid == a+1) { a++; switch (typeid) { { case 1: nslog(@"inside case 1"); dispatch_sync(myq1, ^{ //doesn't wait nstimer finish, //letting double drive forward 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 loop, , count %lu", a); } }); }
drive commands:
//go forward x seconds. - (void)jdriveforward { shoulddriveforward_ = yes; //sets condition recognized callback function run 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; } }
provided drive function experimental api i'm using
i'm using "token" such "shoulddriveforward_" inside function drivedoubleshouldupdate true duration of nstimer. must call drive methods inside function robot not default idle mode. whenever true x duration, function driving forwards or backwards active.
- (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 custom functions //the nstimer i'm using keep bool values below true x seconds, //making robot go forward/backward through callback //method, 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]; } }
you're kind of jumbled here combination of gcd , nstimer
. there's nothing can't intermixed, all-gcd approach might easier head around. think i've discerned gist of you're trying here, , hacked might helpful. i've put the whole project on github, here's meat of it:
#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]; // 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 { // safety... mutable array caller continue mutate commands = [commands copy]; // queue our actual work dispatch_queue_t execqueue = dispatch_queue_create(null, dispatch_queue_serial); // we'll target main queue because simplifies updating of ui dispatch_set_target_queue(execqueue, dispatch_get_main_queue()); // we'll use queue serve commands 1 @ time... dispatch_queue_t latchqueue = dispatch_queue_create(null, dispatch_queue_serial); // have target execqueue; not strictly necessary codifies relationship dispatch_set_target_queue(latchqueue, execqueue); // timer update our ui @ 60fps give or take, on 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 latch queue until we're ready go dispatch_suspend(latchqueue); // first thing command stream start ui updates dispatch_async(latchqueue, ^{ dispatch_resume(timer); }); // next enqueue each command in array (drcommand* cmd in commands) { dispatch_async(latchqueue, ^{ // stop queue processing other commands. dispatch_suspend(latchqueue); // update "machine state" self.currentlyexecutingcommand = cmd; self.currentcommandstarted = @([nsdate timeintervalsincereferencedate]); // set event that'll mark end of command. dispatch_after(dispatch_time(dispatch_time_now, (int64_t)(cmd.duration * nsec_per_sec)), execqueue, ^{ // clear out machine state next command self.currentlyexecutingcommand = nil; self.currentcommandstarted = nil; // resume latch queue next command starts dispatch_resume(latchqueue); }); }); } // after commands have finished, add cleanup block stop timer, , // make sure ui doesn't have stale text in it. dispatch_async(latchqueue, ^{ dispatch_source_cancel(timer); [self updateui]; }); // queued up, start command queue dispatch_resume(latchqueue); } - (void)updateui { // make sure ever update ui on 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
let me know in comment if there's part you'd more explanation on.
note: other answer here has command doing sleep
; approach asynchronous. approach right depend on commands actually doing wasn't clear question.
Comments
Post a Comment