This question already has answers here:
Prevent dispatch_after() background task from being executed
(11 answers)
Closed 8 years ago.
Is there a way to cancel dispatch_after() scheduled for some time in future, and haven't fired so far?
I'm trying to make something like a scheduler for updates from server, and this method is just like I want, but, I'd love to cancel and re-schedule it at some point.
Is it possible at all or I have to fallback and use NSTimer?
There is NO way to prevent a dispatch_block from executing once it has been dispatch to it's queue, meaning that your dispatch_after cannot be canceled. Only option is to add in your block a condition to be checked at runtime to prevent execution.
ie.
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC), dispatch_get_main_queue(), ^ {
if(self.shouldExecuteDispatchBlock)
{ // do your stuff } });
OK, so, with all answers collected, and possible solutions, seems like the best one for this case (preserving simplicity) is calling performSelector:withObject:afterDelay: and cancelling it with cancelPreviousPerformRequestsWithTarget: call when desired. In my case - just before scheduling next delayed call:
[NSObject cancelPreviousPerformRequestsWithTarget: self selector:#selector(myDelayedMethod) object: self];
[self performSelector:#selector(myDelayedMethod) withObject: self afterDelay: desiredDelay];
For this purpose i used this class:
https://github.com/SebastienThiebaud/dispatch_cancelable_block
you can call a cancel() function to revoke the execution of what's in the block.
Use a dispatch timer source (that is what dispatch_after uses internally anyway).
A dispatch timer source can be canceled or its timer parameters changed after creation.
Related
I have function ,
-(void)serverFetch{
//server fetch
}
In every 15mintutes, i'm calling this method using NSTimer,
[NSTimer scheduledTimerWithTimeInterval:900.0f repeats:YES block:^(NSTimer * _Nonnull timer) {
[self fetchFromServer];
}];
I'm using APNS in my app, so when we receive the notification , again i'm calling this method.
So Scheduler thread and this notification thread should not happen in parallel. For instance, when scheduler thread is in operation and push notification arrives then push notification thread should wait for scheduler thread.How can i achieve this?Any help appreciated?
One approach would be to use Grand Central Dispatch (GCD). Create a serial queue and add blocks to it for asynchronous execution when your timer fires or notifications arrive, the blocks will be executed strictly one after the other. This will only work correct if the work each block does is completely synchronous, that is when the block returns all its work is complete.
If your blocks contain asynchronous work then you will also need a semaphore. A block should acquire the semaphore when it starts execution and its final asynchronous action should release it. In this way though the block scheduled by the serial queue returns and the queue starts the next block that next block will immediately block waiting to acquire the semaphore until the previous block's last asynchronous action releases it.
If after studying GCD, designing a solution, and implementing it you have a problem ask a new question, show the code you have written, and explain the problem. Someone will undoubtedly help you move forward.
HTH
I've used both GCD and performSelectorOnMainThread:waitUntilDone in my apps, and tend to think of them as interchangeable--that is, performSelectorOnMainThread:waitUntilDone is an Obj-C wrapper to the GCD C syntax. I've been thinking of these two commands as equivalent:
dispatch_sync(dispatch_get_main_queue(), ^{ [self doit:YES]; });
[self performSelectorOnMainThread:#selector(doit:) withObject:YES waitUntilDone:YES];
Am I incorrect? That is, is there a difference of the performSelector* commands versus the GCD ones? I've read a lot of documentation on them, but have yet to see a definitive answer.
As Jacob points out, while they may appear the same, they are different things. In fact, there's a significant difference in the way that they handle sending actions to the main thread if you're already running on the main thread.
I ran into this recently, where I had a common method that sometimes was run from something on the main thread, sometimes not. In order to protect certain UI updates, I had been using -performSelectorOnMainThread: for them with no problems.
When I switched over to using dispatch_sync on the main queue, the application would deadlock whenever this method was run on the main queue. Reading the documentation on dispatch_sync, we see:
Calling this function and targeting
the current queue results in deadlock.
where for -performSelectorOnMainThread: we see
wait
A Boolean that specifies whether the
current thread blocks until after the
specified selector is performed on the
receiver on the main thread. Specify
YES to block this thread; otherwise,
specify NO to have this method return
immediately.
If the current thread is also the main
thread, and you specify YES for this
parameter, the message is delivered
and processed immediately.
I still prefer the elegance of GCD, the better compile-time checking it provides, and its greater flexibility regarding arguments, etc., so I made this little helper function to prevent deadlocks:
void runOnMainQueueWithoutDeadlocking(void (^block)(void))
{
if ([NSThread isMainThread])
{
block();
}
else
{
dispatch_sync(dispatch_get_main_queue(), block);
}
}
Update: In response to Dave Dribin pointing out the caveats section ondispatch_get_current_queue(), I've changed to using [NSThread isMainThread] in the above code.
I then use
runOnMainQueueWithoutDeadlocking(^{
//Do stuff
});
to perform the actions I need to secure on the main thread, without worrying about what thread the original method was executed on.
performSelectorOnMainThread: does not use GCD to send messages to objects on the main thread.
Here's how the documentation says the method is implemented:
- (void) performSelectorOnMainThread:(SEL) selector withObject:(id) obj waitUntilDone:(BOOL) wait {
[[NSRunLoop mainRunLoop] performSelector:selector target:self withObject:obj order:1 modes: NSRunLoopCommonModes];
}
And on performSelector:target:withObject:order:modes:, the documentation states:
This method sets up a timer to perform the aSelector message on the current thread’s run loop at the start of the next run loop iteration. The timer is configured to run in the modes specified by the modes parameter. When the timer fires, the thread attempts to dequeue the message from the run loop and perform the selector. It succeeds if the run loop is running and in one of the specified modes; otherwise, the timer waits until the run loop is in one of those modes.
GCD's way is suppose to be more efficient and easier to handle and is only available in iOS4 onwards whereas performSelector is supported in the older and newer iOS.
I'm setting a timer so that after a second passes I reset a value for my keyboard extension. The problem is that I feel like the following call is stalling my UI:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[self resetDoubleTapBool];
})
Is there an asynchronous way of doing this, or a better way in general? Thanks!
The dispatch_after() call itself does not block. At (or shortly after) the appointed time, the block will be submitted to the main queue. Submitting it doesn't block the main thread. When the main thread next runs its run loop or is idle within dispatch_main(), it will execute the block.
IF your -resetDoubleTapBool method takes any appreciable amount of time, that can stall your UI. That's just the same as any code that runs on the main thread. It's not specific to dispatch_after() or any other part of GCD.
According to the function documentation:
This function waits until the specified time and then asynchronously adds block to the specified queue.
I am new to IOS development and am currently facing a problem.
When method A is called, it calls method B and then it wait for delegate connectionDidFinish which connectionDidFinish will execute MethodC.
My question is how do I ensure that methodA to methodC has finished executing before executing NSLog?
I found that a way to solve this problem is to use notification center. Send notification to me after finishing executing methodC. I don't think this is a good solution. Is there another way to do this?
Example:
[a methodA];
NSLog(#"FINISH");
If any of those methods perform actions asynchronously, you can't. You'll have to look into a different way of doing this. I personally try to use completion blocks when ever I can, although it's perfectly fine to do this other ways, like with delegate methods. Here's a basic example using a completion block.
- (void)someMethod
{
[self methodAWithCompletion:^(BOOL success) {
// check if thing worked.
}];
}
- (void)methodAWithCompletion:(void (^) (BOOL success))completion
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, kNilOptions), ^{
// go do something asynchronous...
dispatch_async(dispatch_get_main_queue(), ^{
completion(ifThingWorked)
});
});
}
In the code you posted, methodA must finish executing before the log statement will execute.
However, if methodA starts an asynchronous process that takes a while to finish and returns before it is finished, then you need to do something different. Usually you don't want to freeze the user interface while you are waiting, so you set up a delegate, pass in a completion block, or wait for an "ok, I'm done" notification.
All those are very valid, good ways to solve the problem of waiting for asynchronous tasks to finish running.
Newer APIs are starting to use completion blocks. Examples are:
presentViewController:animated:completion:, which takes a completion
block that gets called once the new view controller is fully
on-screen and "ready for business.
animateWithDuration:animations:completion:, which takes a completion
block that gets executed once the animation is finished, and
sendAsynchronousRequest:queue:completionHandler:, which starts an
asynchronous URL request (usually an HTTP GET or PUT request) and
provides a completion block that gets called once the request has
been completed (or fails)
I have some code that uses a fair amount of GCD code. I need to implement a way to schedule a unit of work after some delay, but can be canceled and moved further out if needed.
Think of a handler for clicks; something to distinguish single clicks from double clicks. To do this, one would get the click event, and set up a short timer to act on it. If another click event came through before the timer fired, it would be canceled and started again. When the timer did eventually fire, it would have the correct number of clicks.
Anyways, this would be easy enough to implement with NSTimers or the performSelector stuff on NSObject. Maybe something like
NSUInteger tapCount = 0;
- (void)handleClickEvent
{
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(fireEvent:) object:nil];
tapCount++;
[self performSelector:#selector(fireEvent:) withObject:#(tapCount) afterDelay:2.0];
}
- (void)fireEvent:(NSNumber *)clickCount
{
// Act on the coalesced event
NSUInteger numClicks = [clickCount unsignedIntegerValue];
if ( numClicks == 1 ) // single click
if ( numClicks == 2 ) // double click
}
Before doing it this way however, I am wondering if there is a way to do this with the GCD functions. I know you can't undo enqueueing a block, so dispatch_after isn't really an option. I know there are dispatch timer sources, but they seem like they are more used for firing periodic tasks. I don't know if they can be easily canceled and started later like I would need.
Thanks for any suggestions.
dispatch_source_set_timer() will reschedule a dispatch timer source (which can be one-shot and non-repeating if you pass DISPATCH_TIME_FOREVER as the interval parameter).
Note that this API is non-preemptive, i.e. unless you call dispatch_source_set_timer() from the target queue of the timer source, the timer handler could already be running at the time of reschedule.
However, once dispatch_source_set_timer() returns, it is guaranteed that the timer will no longer fire at the previously set target time.