beginBackgroundTaskWithExpirationHandler creates a new thread when we execute something.
Can I execute something using an existing thread with this?
Because new thread generated by beginBackgroundTaskWithExpirationHandler causes some problems to my application when it is resumed. So I passed an instance of an existing thread to beginBackgroundTaskWithExpirationHandler and called the required methods using the existing thread. Is it ok to use existing threads inside beginBackgroundTaskWithExpirationHandler ?
Will it cause any problems?
- (void)applicationDidEnterBackground:(UIApplication *)application {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
IOSMobilePOSApplication *app = [IOSMobilePOSApplication getInstance];
if ([app keepAliveMessage]) {
if([[UIDevice currentDevice] respondsToSelector:#selector(isMultitaskingSupported)])
{
[Logger log:#"Multitasking Supported"];
background_task = [application beginBackgroundTaskWithExpirationHandler:^ {
[Logger log:#"Background maximum time exeeded."];
IOSMobilePOSApplication *iosMobileApplication = [IOSMobilePOSApplication getInstance];
[[iosMobileApplication getKeepAliveManager] stop];
//Clean up code. Tell the system that we are done.
[application endBackgroundTask: background_task];
background_task = UIBackgroundTaskInvalid;
}];
//To make the code block asynchronous
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//### background task starts
[Logger log:#"Running in the background"];
IOSMobilePOSApplication *iosMobileApplication = [IOSMobilePOSApplication getInstance];
[[iosMobileApplication getKeepAliveManager] setEnabled:true];
[[iosMobileApplication getKeepAliveManager] performSelector:#selector(run) onThread:[[iosMobileApplication getKeepAliveManager] runingThread] withObject:NULL waitUntilDone:NO];
isStarted = true;
//#### background task ends
//Clean up code. Tell the system that we are done.
[application endBackgroundTask: background_task];
background_task = UIBackgroundTaskInvalid;
});
}
else
{
[Logger log:#"Multitasking Not Supported"];
}
}
}
here
[[iosMobileApplication getKeepAliveManager] run]; causes new thread to start and it causes thread synchronization problems in my code. So I added the code in place of above mentioned line.
[[iosMobileApplication getKeepAliveManager] performSelector:#selector(run) onThread:[[iosMobileApplication getKeepAliveManager] runingThread] withObject:NULL waitUntilDone:NO];.
Will this cause any problems when the app goes background?
It’s probably some mistake, because -beginBackgroundTaskWithExpirationHandler: doesn’t create any threads. It only marks the beginning of some long-running task you’re about to start or just started.
This method can be called from any thread. The expiration handler that it takes as a parameter is called on the main thread. This handler is used to clean up and mark the end of the long-running task you’re doing.
Related
Suppose I have a method that does some asynchronous tasks. Let's say it refreshes user's access permission and it may take several minutes depending on the internet connection speed or whatever.
I have to call this method periodically (i.e. scheduled call using NSTimer's method scheduledTimerWithTimeInterval: target: selector: userInfo: repeats:)
-(void)refreshPermission {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// do something that takes a few minutes
});
}
Now I call this method as timer = [NSTimer scheduledTimerWithTimeInterval:10 target:self selector:#selector(refreshPermission) userInfo:nil repeats:YES];. That is, this call is fired every 10 seconds.
What I need to do is, I need to somehow skip one (or, more than one)
scheduled call to this method if something is happening inside that
asynchronous block (Let's say, user's access permission hasn't been
updated).
But once the block is done (that is, user's access permission has been updated), scheduled call with timer should resume.
Any idea or any sample on how to accomplish this??
I think you can do it by using a Bool variable. You can declare Bool variable globally and by using its state you can manage your task in function call.
In method refreshPermission
-(void)refreshPermission {
if(!isExecuting){
isExecuting = YES;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Perform your tasks
isExecuting = NO;
}
}
}
I've come up with this approach. Got the idea from #Sunny's answer.
It worked for me. But any suggestion regarding this implementation is appreciated.
-(void)refresh {
NSLog(#"Refresh called");
NSLock *theLock = [[NSLock alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(#"Async task assigned");
if(!isExecuting){
[theLock lock];
isExecuting = YES;
[theLock unlock];
// Perform your tasks
NSLog(#"Async task started");
[NSThread sleepForTimeInterval: 13.0]; // for testing purpose
NSLog(#"Async task completed");
[theLock lock];
isExecuting = NO;
[theLock unlock];
}
});
}
Here isExecuting is an instance variable of the containing class. And it was set to isExecuting = NO; before setting the actual scheduled timer for calling the method periodically.
Here I used NSLock for the assurance that no other thread can change the value of isExecuting while a thread is in execution of it's task. I added this locking because every time -(void)refresh method is invoked, there is a possibility that multiple threads become eligible for the execution and changing value of isExecuting. So it's better to make it thread save when changing value of shared variable.
For code like:
// Code in some object that will do work for an application:
- (BOOL)shouldBeRunning
{
[lockRunning lock];
BOOL shouldBeRunning= shouldRun;
[lockRunning unlock];
return shouldBeRunning;
}
- (void)stopRunning
{
[lockRunning lock];
shouldRun= FALSE;
[lockRunning unlock];
}
- (void)threadEntryPoint:(id)object
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
// From an example I saw awhile back:
// A runloop with no sources returns immediately from runMode:beforeDate:
// That will wake up the loop and chew CPU. Add a dummy source to prevent it.
NSMachPort *dummyPort = [[NSMachPort alloc] init];
[runLoop addPort:dummyPort forMode:NSDefaultRunLoopMode];
[dummyPort release];
[pool release];
while ([self shouldBeRunning])
{
NSAutoreleasePool *loopPool = [[NSAutoreleasePool alloc] init];
[runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
[loopPool drain];
}
}
- (BOOL)startRunning:(NSError **)errorPtr
{
[self stopRunning]; // Stop if we are already running.
[runWorker release];
runWorker= [[NSThread alloc] initWithTarget:self selector:#selector(threadEntryPoint:) object:nil];
if(!runWorker)
return (FALSE);
// Start up the thread.
shouldRun= TRUE;
[runWorker start];
return TRUE;
}
- (void)doLotsOfStuff
{
// Some operation that is long and intensive
// that should be done in the background.
// This function will call the app delegate, which will display the
// results. It will also notify the app on completion.
}
- (void)doStuff
{
// Commented out for illustrative purposes.
//[self startRunning]; // Fire thread up.
[self performSelector:#selector(doLotsOfStuff) onThread:runWorker withObject:nil waitUntilDone:NO];
}
// Out in the delegate:
- (void)applicationDidFinishLaunching:(NSNotification*)aNotification
{
// Do setup....
[workObject startRunning]; // Start the worker thread in the worker object.
}
- (void)buttonHandler:(id)sender
{
[workObject doStuff];
}
So, in the application there is a button. The user will press it, and a task will run on a worker thread. The task will provide feedback to the application. In this case, the button is disabled until the task completes. I just do not want to show all that code.
With the code as written, if I press the button once, the task runs without delay. Often, a second button press yields the same result. However, sometimes the second press, but almost always the third or after, will result in a significant delay in performing the task. I put debug statements in and can observe that the code does the performSelector on the thread, then there is a delay, and finally the task runs.
If I uncomment the line in doStuff that re-creates the thread (making the one in applicationDidFinishLaunching redundant), of course it works perfectly every time.
From what I can tell, the thread is getting into an unresponsive state.
Any ideas on what might be going on? Anything obviously wrong with the setup and handling code? Any input appreciated.
I have a Download object which handles NSURLConnection.
Then I have NSOperation object (DownloadOperation) which holds Download object as property.
Download object has ability to start/pause/resume/cancel.
This is the main method of DownloadOperation
- (void)main
{
#autoreleasepool {
BOOL isDone = NO;
if (![self isCancelled]) {
[_download start]; //Download object start (creates NSURLConnection internally)
}
NSDate *distantFuture = [NSDate distantFuture];
while(!isDone && ![self isCancelled]) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:distantFuture];
if (!_download.isActive) { //internal state of Download
isDone = YES;
}
}
[self completeOperation]; //this sets finished and executing flags
}
}
From outside (from UI), I manipulate with Download object: Start, Pause, Resume, Cancel.
And internally I am changing its state so when Download is finished or canceled, isActive is set to NO and while loop should end.
This works if I start Download and let it finish (in background, NSURLConnection finished and called delegate -connectionDidFinish...)
If I pause/resume Download, download will continue to download and finish (and change its internal state: isActive -> NO).
On pause I cancel NSURLConnection and on resume I create new.
Or if I cancel the download, it will also be inactive (NSURLConnection is canceled).
But here is the problem:
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:distantFuture]; never returns in those cases (when I cancel NSURLConnection) and my "if" statement is never handled so this DownloadOperation is always considered running and will never exit my NSOperationQueue.
It looks like there is no event that could be fired that would cause that runloop to wake up.
I tried
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.05]];
and this kind of works but not so smooth and I don't think it is the best solution.
What I really want to know is, how to force that NSRunLoop to wake up (how to fire some event that will cause it to wake up) and continue my while loop?
Here's what I did.
Good thing is that I have notifications in my app and I know exactly when downloads are changing. So I've made an instance variable NSThread currentThread and at the beginning of main I call currentThread = [NSThread currentThread].
After I receive notification which I know should cause my thread to wake up I call:
[self performSelector:#selector(wakeUpThread:) onThread:currentThread withObject:nil waitUntilDone:NO];
- (void)wakeUpThread does nothing, it is empty method, but purpose of this is to wake up thread and cause my run loop to continue.
Here's a weird one. My application sends a shutdown message to an object controlling a hardware device, with a completion block as an argument. The shutdown message returns a BOOL, depending on whether it was able to complete shutdown immediately. So YES means it's done now, NO means it will invoke the completion handler when it's done later.
Here's the main controller code:
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
{
BOOL shutdownNow = [theStoker shutdownWithCompletionHandler:^(void)
{
NSLog(#"applicationShouldTerminate: completionBlock");
[[NSRunningApplication currentApplication] terminate];
}];
if (!shutdownNow)
{
NSLog(#"applicationShouldTerminate: waiting for shutdown");
return NSTerminateCancel;
}
return NSTerminateNow;
}
Here's the device controller code:
- (BOOL)shutdownWithCompletionHandler:(void (^)(void))handler
{
if (busy)
{
self.completionBlock = handler;
[self stopDevice];
NSLog(#"shutdownWithCompletionHandler: Wait for reset");
return NO;
}
NSLog(#"Stoker: shutdownWithCompletionHandler: shutdown now");
return YES;
}
The weird part is that the shutdown messages to be sent twice. These are the NSLog messages:
shutdownWithCompletionHandler: Wait for reset
applicationShouldTerminate: waiting for reset
applicationShouldTerminate: completionBlock
shutdownWithCompletionHandler: shutdown now
So after the completion block runs, I end up back in the device object's shutdownWithCompletionHandler: method. Why?
Edit: Hmmm. Does the [[NSRunningApplication currentApplication] terminate]; cause the
applicationShouldTerminate: to get called again? I think it must. Is there a better way to exit the app in the completion handler?
Since you have found the underlying cause of your problem (calling terminate causes applicationShouldTerminate:), here is how to avoid it.
Instead of canceling the termination, return NSTerminateLater when it will shutdown later. Then, your completion block should call [NSApp replyToApplicationShouldTerminate:YES] to trigger the termination, without calling applicationShouldTerminate: again.
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender {
BOOL shutdownNow = [theStoker shutdownWithCompletionHandler:^(void) {
NSLog(#"applicationShouldTerminate: completionBlock");
[NSApp replyToApplicationShouldTerminate:YES];
}];
if (!shutdownNow) {
NSLog(#"applicationShouldTerminate: waiting for shutdown");
return NSTerminateLater;
}
return NSTerminateNow;
}
My application has a second running thread. I need to achieve the following :
Stop the separate thread gracefully from the main application thread
Call a function on the main thread from the second thread to signal a result has been found and pass it to the main one.
I've found the following for the first task : share a global variable between the 2 threads ?
No idea how to achieve the second task. (NSNotificationCenter doesn't allow to pass objects ...)
I'm lunching the second thread like this [NSThread detachNewThreadSelector:#selector(backGroudTask) toTarget:self withObject:nil];
Thanks
I'm still searching for the best answer to this, but here is what I do:
Use NSLock to create a lock that prevents me from accessing the same variable on both threads. Then use a BOOL to see if the main thread wants to initiate a stop.
in main thread do this
[myLock lock];
exitFlag = YES;
[myLock unlock];
in the other thread do this
endMe = NO;
while(!endMe)
{
// do your task stuff
[myLock lock];
endMe = exitFlag;
[myLock unlock];
}
For the second part of your question use the following:
[self performSelectorOnMainThread:#selector(your_selector_name) withObject:nil waitUntilDone:false];
This will cause the your selector routine to run on the main thread.
Hope this helps
(NSNotificationCenter doesn't allow to pass objects ...)
it does, but you have to add them to the userinfo of the notification
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:myObject forKey:#"object"];
[[NSNotificationCenter defaultCenter] postNotificationName:#"myNotification" object:self userInfo:userInfo];
- (void)foo:(NSNotification *)notification {
id object = [[notification userInfo] objectForKey:#"object"];
}