Can i use GCD for background thread and performSelector for resume back to main thread? [duplicate] - ios

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.

Related

How can I "visualize"(observe) a thread exiting in iOS?

When learning thread and run loop, I notice that some articles say: "Generally, a thread just exits once it has done its work." So there is necessity sometimes to create a so-called "immortal thread"(?? I don't know the exact term in English) using NSRunloop.
The question is HOW can I prove the statement "just exits once it has done its work"? I code like this
- (void)doSomethingOnThread {
// dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// NSLog(#"I'm on thread %#", [NSThread currentThread]);
// });
NSThread *thread1 = [[NSThread alloc] initWithBlock:^{
NSLog(#"I'm on thread %#", [NSThread currentThread]);
}];
thread1.name = #"thread1";
[thread1 start];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(threadExitHandler:) name:NSThreadWillExitNotification object:nil];
}
- (void)threadExitHandler:(NSNotification *)noti {
NSLog(#"Thread will exit: %#", noti);
}
Well, the notification handler is not called.
So, [1]: How can I prove a thread exiting? [2]: What kinds of threads behave so?(I know the main thread will never exit, what about other thread? GCD threads, for example?)
If you want to visualize it, I might use the debugger. For example, I've set a breakpoint inside a NSThread subclass and I see the thread listed in the left panel in Xcode:
But if I have another breakpoint triggered one second after the main method finishes, I see the relevant “Thread will exit” message and my thread is no longer present :
Or you could add a NSLog statement inside the dealloc method for your NSThread subclass, and that also would demonstrate its deallocation. Or look for the subclass in the debug memory object graph.
Well, the notification handler is not called.
I'd suggest you add your observer for NSThreadWillExitNotification before you start your thread. Right now you have a race condition between the starting and exiting of this thread and the adding of the observer. FWIW, I do see the “Thread will exit” message.
Unrelated, while it’s great to learn about threads and runloops, it has little practical use nowadays. It might be more useful to master GCD which gets us out of the weeds of threads and offers performance optimizations and a richer API for writing robust multi-threaded code.
In regards to whether GCD creates persistent threads or not, the answer is yes, but we're abstracted away from this detail. But one of GCD’s performance optimizations is that it manages a “pool” of threads for us, not constantly spinning up new threads and destroying them constantly for every dispatched block of code.
You might want to watch WWDC 2016’s Concurrent Programming With GCD in Swift 3. It walks through the relationship between queues, threads, and runloops.

performSelectorWithDelay in serialQueue

I've a serial queue and I use that queue to call a performSelectorWithDelay like below
dispatch_async(serialQueue, ^(void) {
[self performSelector:#selector(fetchConfigFromNetwork) withObject:nil afterDelay:rootConfig.waitTime];
});
However, the method fetchConfigFromNetwork never gets called. However, if instead of serialQueue, I use mainQueue - it starts working.
Cannot understand what's happening here and how to fix it?
The explanation why your code doesn't work is in the documentation: https://developer.apple.com/documentation/objectivec/nsobject/1416176-performselector?language=occ
This method registers with the runloop of its current context, and
depends on that runloop being run on a regular basis to perform
correctly. One common context where you might call this method and end
up registering with a runloop that is not automatically run on a
regular basis is when being invoked by a dispatch queue. If you need
this type of functionality when running on a dispatch queue, you
should use dispatch_after and related methods to get the behavior you
want.
I'm assuming you want that method to be called on the serial queue with a delay. The most straight forward (and recommended way) is to use dispatch_after:
__weak typeof(self) wself = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(rootConfig.waitTime * NSEC_PER_SEC)), serialQueue, ^{
[wself fetchConfigFromNetwork];
});
This method sets up a timer to perform the aSelector message on the current thread’s run loop. The timer is configured to run in the default mode (NSDefaultRunLoopMode). 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 the default mode; otherwise, the timer waits until the run loop is in the default mode.
This is the discussion about the method performSelector:withObject:afterDelay:, I think the block of dispatch_async will execute on a new thread (not main thread), but you would not know which thread it is, so you can not new a runloop and open it and assign it to this thread. because the runloop of thread is close in default except the main thread, the timer will wait forever.
On my opinion, you should use NSThread instead of dispatch_async, and create a runloop for the thread that you use, then specified the mode of runloop with NSDefaultRunLoopMode, if you actually want to cancelPreviousPerformRequestsWithTarget, otherwise use dispatch_after instead of performSelector.
That's my understanding. I can't promise it is right.

Which queue is used for -[NSObject performSelector:withObject:afterDelay]?

I recently ran into an issue where deferred selectors weren't firing (an NSTimer and methods called with performSelector:withObject:afterDelay).
I've read Apple's documentation, and it does mention in the special considerations area,
This method registers with the runloop of its current context, and depends on that runloop being run on a regular basis to perform correctly. One common context where you might call this method and end up registering with a runloop that is not automatically run on a regular basis is when being invoked by a dispatch queue. If you need this type of functionality when running on a dispatch queue, you should use dispatch_after and related methods to get the behavior you want.
This makes perfect sense, except for the runloop of its current context part. I found myself confused regarding which runloop it's actually going to. Would it be the thread's main runloop that processes all events, or could it be a different one without our knowledge?
For instance, if I hit a breakpoint before calling performSelector inside a block that is being called as a CoreAnimation completion block, the debugger shows execution is on the main thread. However, calling performSelector:withObject:afterDelay never actually runs the selector. This makes me think that call is effectively registering with the runloop associated with the CoreAnimation framework, so regardless of the performSelector call being executed on the main thread, if the CoreAnimation doesn't poll its runloop, the operation isn't executed.
Replacing this call inside that block with performSelectorOnMainThread:WithObject:waitUntilDone fixes the problem, but I've had a hard time convincing a colleague that this is the root cause.
Update: I was able to trace back the origin of the issue to a UIScrollViewDelegate callback. It makes sense that when a UI delegate callback is invoked that the main runloop would be in UITrackingRunLoopMode. But at that point, the handler will queue a block on a background queue and from there execution will jump across a few other queues, eventually coming back to the main runloop. The catch is that when it comes back to the main runloop, it's still in UITrackingRunLoopMode. I think that the main runloop should have come out of UITracking mode when the delegate method was completed, but when execution gets back to main runloop, it's still in that mode. Deferring the code that kicks off the background queueing of the job from the UIScrollViewDelegate method fixes the problem, e.g [self performSelector:#selector(sendTaskToBackQueue) withObject:nil afterDelay:0 inModes:#[NSDefaultRunLoopMode]]. Is it possible that the runloop mode that is used when the background task is queued back to the main thread is dependent on the mode the runloop was in when it queued the background task?
Essentially, the only change was going from this...
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
// Currently in UITrackingRunLoopMode
dispatch_async(someGlobalQueue, someBlock);
// Block execution hops along other queues and eventually comes back to main runloop and will still be in tracking mode.
}
to this
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
// Currently in UITrackingRunLoopMode
[self performSelector:#selector(backQueueTask) withObject:nil afterDelay:0 inModes:#[NSDefaultRunLoopMode]];
}
-(void)backQueueTask {
// Currently in NSDefaultRunLoopMode
dispatch_async(someGlobalQueue, someBlock);
// Hops along other queues and eventually comes back to main runloop and will still be in NSDefaultRunLoopMode.
// It's as if the runloop mode when execution returns was dependent on what it was when the background block was queued.
}
There is only one run loop per thread, so if you're on the main thread then you're also on the main run loop. However, a run loop can run in different modes.
There are a few things you can try to get to the bottom of the issue:
You can use +[NSRunLoop currentRunLoop] and +[NSRunLoop mainRunLoop] to verify that you're executing from the main thread and main run loop.
You can also use the current run loop directly with NSTimer to schedule a delayed perform-selector. E.g.:
void (^completionBlock)(BOOL) = ^(BOOL finished) {
NSCAssert([NSRunLoop currentRunLoop] == [NSRunLoop mainRunLoop], #"We're not on the main run loop");
NSRunLoop* runLoop = [NSRunLoop mainRunLoop];
// Immediate invocation.
[runLoop performSelector:#selector(someMethod) target:self argument:nil order:0 modes:#[NSDefaultRunLoopMode]];
// Delayed invocation.
NSTimer* timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:#selector(someMethod) userInfo:nil repeats:NO];
[runLoop addTimer:timer forMode:NSDefaultRunLoopMode];
};
Those calls are essentially equivalent to -performSelector:withObject: and -performSelector:withObject:afterDelay:.
This allows you to confirm which run loop you're using. If you're on the main run loop and the delayed invocation doesn't run, it's possible that the main run loop is running in a mode that doesn't service timers in the default mode. For example, that can happen when a UIScrollView is tracking touch input.
-performSelector:withObject:afterDelay: doesn't schedule operations on a dispatch queue; it schedules it on the run loop of the current thread. Each thread has one run loop, but somebody has to run the run loop in order for it to execute the actions on it. So it all depends on what thread this code is run on.
If it is run on the main thread, the operation will be scheduled on the main thread's run loop. In event-based applications, UIApplicationMain is called in the main function, which runs a run loop on the main thread for the entire lifetime of the app.
If this is run on another thread that you created, then the operation will be put on that thread's run loop. But unless you explicitly run the thread's run loop, the operations scheduled on the run loop won't run.
If this is run on a GCD dispatch queue, it means it is running on some unknown thread. GCD dispatch queues manage threads internally in a way that is opaque to the user. Generally nobody would have run the run loop on such a thread, so operations scheduled on the run loop won't run. (Of course, you could explicitly run the run loop in the same place that you schedule the operation, but that would block the thread, and thus block the dispatch queue, which wouldn't make that much sense.)
performSelector:withObject:afterDelay this will call the selector on the thread that this function is called.
performSelectorOnMainThread:WithObject:waitUntilDon,this will make sure that the selector is called on main thread
What is run loop:
Run loops are part of the fundamental infrastructure associated with threads. A run loop is an event processing loop that you use to schedule work and coordinate the receipt of incoming events. The purpose of a run loop is to keep your thread busy when there is work to do and put your thread to sleep when there is none.

NSOperation deadlocks and blocks NSOperationQueue

I use a subclass of NSOperation to upload large files to AWS S3 using Amazon's iOS SDK (v1.3.2). This all works fine, but some beta testers experience deadlocks (iOS 5.1.1). The result is that the NSOperationQueue in which the operations are scheduled is blocked as only one operation is allowed to run at one time. The problem is that I cannot reproduce the issue whereas the beta testers experience this problem every single time.
The operation is quite complex due to how the AWS iOS SDK works. However, the problem is not related to the AWS iOS SDK as far as I know based on my testing. The operation's main method is pasted below. The idea of the operation's main method is based on this Stack Overflow question.
- (void)main {
// Operation Should Terminate
_operationShouldTerminate = NO;
// Notify Delegate
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate operation:self isPreparingUploadWithUuid:self.uuid];
});
// Increment Network Activity Count
[self incrementNetworkActivityCount];
// Verify S3 Credentials
[self verifyS3Credentials];
while (!_operationShouldTerminate) {
if ([self isCancelled]) {
_operationShouldTerminate = YES;
} else {
// Create Run Loop
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
}
// Decrement Network Activity Count
[self decrementNetworkActivityCount];
NSLog(#"Operation Will Terminate");
}
The method that finalizes the multipart upload sets the boolean _operationShouldTerminate to YES to terminate the operation. That method looks like this.
- (void)finalizeMultipartUpload {
// Notify Delegate
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate operation:self didFinishUploadingUploadWithUuid:self.uuid];
});
// Operation Should Terminate
_operationShouldTerminate = YES;
NSLog(#"Finalize Multipart Upload");
}
The final log statement is printed to the console, but the while loop in the main method does not seem to exit as the final log statement in the operation's main method is not printed to the console. As a result, the operation queue in which the operation is scheduled, is blocked and any scheduled operations are not executed as a result.
The operation's isFinished method simply returns _operationShouldTerminate as seen below.
- (BOOL)isFinished {
return _operationShouldTerminate;
}
It is odd that the while loop is not exited and it is even more odd that it does not happen on any of my own test devices (iPhone 3GS, iPad 1, and iPad 3). Any help or pointers are much appreciated.
The solution to the problem is both complex and simple as it turns out. What I wrongly assumed was that the methods and delegate callbacks of the operation were executed on the same thread, that is, the thread on which the operation's main method was called. This is not always the case.
Even though this was true in my test and on my devices (iPhone 3GS), which is why I did not experience the problem myself. My beta testers, however, used devices with multicore processors (iPhone 4/4S), which caused some of the code to be executed on a thread different from the thread on which the operation's main method was invoked.
The result of this is that _operationShouldTerminate was modified in the finalizeMultipartUpload method on the wrong thread. This in turn means that the while loop of the main method was not exited properly resulting in the operation deadlocking.
In short, the solution is to update _operationShouldTerminate on the same thread as the main method was invoked on. This will properly exit the while loop and exit the operation.
There are a number of problems with your code, and I can offer two solutions:
1) read up on Concurrent NSOperations in Apple's Concurrency Programming Guide. To keep the runLoop "alive" you have to add either a port or schedule a timer. The main loop should contain a autorelease pool as you may not get one (see Memory Management in that same memo). You need to implement KVO to let the operationQueue know when your operation is finished.
2) Or, you can adopt a small amount of field tested hardened code and reuse it. That Xcode project contains three classes of interest to you: a ConcurrentOperation file that does well what you are trying to accomplish above. The Webfetcher.m class shows how to subclass the concurrent operation to perform an asynchronous URL fetch from the web. And the OperationsRunner is a small helper file you can add to any kind of class to manage the operations queue (run, cancel, query, etc). All of the above are less than 100 lines of code, and provide a base for you to get your code working. The OperationsRunner.h file provide a "how to do" too.

How to open/create UIManagedDocument synchronously?

As mentioned in title, I would like to open UIManagedDocument synchronously, i.e, I would like my execution to wait till open completes. I'm opening document on mainThread only.
Current API to open uses block
[UIManagedDocument openWithCompletionHandler:(void (^)(BOOL success))];
Locks usage mentioned at link works well on threads other than main thread. If I use locks on mainThread, it freezes execution of app.
Any advice would be helpful. Thanks.
First, let me say that I strongly discourage doing this. Your main thread just waits, and does nothing while waiting for the call to complete. Under certain circumstances, the system will kill your app if it does not respond on the main thread. This is highly unusual.
I guess you should be the one to decide when/how you should use various programming tools.
This one does exactly what you want... block the main thread until the completion handler runs. Again, I do not recommend doing this, but hey, it's a tool, and I'll take the NRA stance: guns don't kill people...
__block BOOL waitingOnCompletionHandler = YES;
[object doSomethingWithCompletionHandler:^{
// Do your work in the completion handler block and when done...
waitingOnCompletionHandler = NO;
}];
while (waitingOnCompletionHandler) {
usleep(USEC_PER_SEC/10);
}
Another option is to execute the run loop. However, this isn't really synchronous, because the run loop will actually process other events. I've used this technique in some unit tests. It is similar to the above, but still allows other stuff to happen on the main thread (for example, the completion handler may invoke an operation on the main queue, which may not get executed in the previous method).
__block BOOL waitingOnCompletionHandler = YES;
[object doSomethingWithCompletionHandler:^{
// Do your work in the completion handler block and when done...
waitingOnCompletionHandler = NO;
}];
while (waitingOnCompletionHandler) {
NSDate *futureTime = [NSDate dateWithTimeIntervalSinceNow:0.1];
[[NSRunLoop currentRunLoop] runUntilDate:futureTime];
}
There are other methods as well, but these are simple, easy to understand, and stick out like a sore thumb so it's easy to know you are doing something unorthodox.
I should also note that I've never encountered a good reason to do this in anything other than tests. You can deadlock your code, and not returning from the main run loop is a slippery slope (even if you are manually executing it yourself - note that what called you is still waiting and running the loop again could re-enter that code, or cause some other issue).
Asynchronous APIs are GREAT. The condition variable approach or using barriers for concurrent queues are reasonable ways to synchronize when using other threads. Synchronizing the main thread is the opposite of what you should be doing.
Good luck... and make sure you register your guns, and always carry your concealed weapons permit. This is certainly the wild west. There's always a John Wesley Harden out there looking for a gun fight.

Resources