How to cancel a worker thread in applicationDidEnterBackground on iOS - iphone

I've heavily researched for a proper solution but it didn't work out functional:
My iPhone app updates data via NSURL requests on user demand. Each file loaded online is only 1.5k in size but one update can consist of 400 of such files. So I do the download stuff in a separate thread which is cancelable and during the update there is an UIAlertView with process indication and cancel button. The thing may run 1...3 minutes, so it can exceed the timeout the device is staying alive or other things happen and my app will go background.
When I do nothing when applicationDidEnterBackground is called, I realize that my app is suspended and also the worker thread. It wakes up and continues work when the app is in foreground again. So far so good. I get into trouble when I press the cancel button after being in foreground again - then the thread reacts as being cancelled (as it should) but immediately runs again and crashes at the end (with error codes somewhere deep in Apple frameworks). It works perfectly to cancel the thread as long as the app keeps staying in foreground - so my idea is to stop/cancel it when applicationDidEnterBackground is entered. I've tried some things to do so but each attempt ends in the fact that the worker thread is suspended at that moment applicationDidEnterBackground is called, so I can't cancel the thread and wait for that. One example I tried is this:
diagView* viewDiagCtrl = startDiag.diagViewControl;
if (viewDiagCtrl != nil && viewDiagCtrl.actionThread != nil)
{
// UIBackgroundTaskIdentifier bgTask is instance variable
NSAssert(self->bgTask == UIBackgroundTaskInvalid, nil);
bgTask = [application beginBackgroundTaskWithExpirationHandler:
^{
dispatch_async(dispatch_get_main_queue(),
^{
NSLog(#"stopping actions definitely");
[viewDiagCtrl stopBackGroundActivity:self];
[application endBackgroundTask:self->bgTask];
self->bgTask = UIBackgroundTaskInvalid;
});
}];
dispatch_async(dispatch_get_main_queue(),
^{
// Start with 10ms time boxes
NSTimeInterval ti = 0.01;
while ([application backgroundTimeRemaining] > 1.0)
{
if (![viewDiagCtrl.actionThread isExecuting])
break;
NSDate* date = [NSDate dateWithTimeIntervalSinceNow:ti];
// Let the current run-loop do it's magif for one time-box.
[[NSRunLoop currentRunLoop] runMode: NSRunLoopCommonModes
beforeDate: date];
// Double the time box, for next try, max out at 1000ms.
ti = MIN(1.0, ti * 2);
}
[viewDiagCtrl stopBackGroundActivity:self];
[application endBackgroundTask:self->bgTask];
self->bgTask = UIBackgroundTaskInvalid;
});
}
I'm not experienced with the whole queue stuff and found such a construct somewhere. I assume that all things dispatched work in the main thread and the worker thread remains suspended so that I don't have any chance to cancel it. Any ideas to come around with that?
I've also read about attempts to do everything without multithreading - but I don't really appreciate that.Is there maybe some useful link to handle the "go background" situation properly?

You can wrap your thread work in the background thread like:
...
[self performSelectorInBackground:#selector(backgroundThread)
withObject:nil];
...
-(void)backgroundThread
{
// do a download once a minute in a background thread - dont let the system suspend us
while(true)
{
BOOL expire = NO;
bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{expire = YES;}];
while(!downloadComplete && !expire)
{
//do your multiple file downloads here;
}
[[UIApplication sharedApplication] endBackgroundTask:bgTask];
[NSThread sleepForTimeInterval:60];
}
}
What this will do is continue the task in progress which will continue when the app is backgrounded - I do this is several places in my heavily threaded/network intensive app.
AFAIK: You don't have to wait until the app is being backgrounded to call beginBackgroundTask - you should call it any time your app has a function it needs to complete without being interrupted/suspended.
I also use NSURLRequest with:
[request setNetworkServiceType:NSURLNetworkServiceTypeVoIP];
(and use the voip background mode in the supportedbackgroundmodes flag)

Related

Application should Active while in Background mode

I want to make my Application Active when Application is in background state, as my NStimer and other operation are performing. So, I did the following code to make my Application Active.
- (void)applicationWillResignActive:(UIApplication *)application
{
UIApplication *app = [UIApplication sharedApplication];
if (backgroundTaskBool==TRUE)
{
bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
dispatch_async(dispatch_get_main_queue(), ^{
if (bgTask != UIBackgroundTaskInvalid)
{
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}
});
}];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Do the work associated with the task.
_locationManager.distanceFilter = 100;
_locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters;
[_locationManager startMonitoringSignificantLocationChanges];
[_locationManager startUpdatingLocation];
dispatch_async(dispatch_get_main_queue(), ^{
if (bgTask != UIBackgroundTaskInvalid)
{
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}
});
});
}
}
I just wanted to know is it a correct way to do? or there will be some other way to achieve this, because this process is taking too much battery and unnecessary location updating. So please help me out from this.
Thanks in advance.
You cant run the task in back ground for longer. IOS allow you to run the background task for 10 min only (after you click home button once your app launched).
Refer this
https://stackoverflow.com/a/5326257/1545180
As your question is tagged for iOS6!
You application cannot continue to run in background unless your application requires any of these features.
Audio—The app plays audible content to the user while in the
background. (This content includes streaming audio or video content
using AirPlay.)
Location—The app keeps users informed of their
location, even while it is running in the background.
VoIP—The app
provides the ability for the user to make phone calls using an
Internet connection.
Newsstand-content—The app is a Newsstand app
that downloads and processes magazine or newspaper content in the
background.

Can we call the method after the application has been minimized?

iOS
Can we call the method after the application has been minimized?
For example, 5 seconds after was called applicationDidEnterBackground:.
I use this code, but test method don't call
- (void)test
{
printf("Test called!");
}
- (void)applicationDidEnterBackground:(UIApplication *)application
{
[self performSelector:#selector(test) withObject:nil afterDelay:5.0];
}
You can use the background task APIs to call a method after you've been backgrounded (as long as your task doesn't take too long - usually ~10 mins is the max allowed time).
iOS doesn't let timers fire when the app is backgrounded, so I've found that dispatching a background thread before the app is backgrounded, then putting that thread to sleep, has the same effect as a timer.
Put the following code in your app delegate's - (void)applicationWillResignActive:(UIApplication *)application method:
// Dispatch to a background queue
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
// Tell the system that you want to start a background task
UIBackgroundTaskIdentifier taskID = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
// Cleanup before system kills the app
}];
// Sleep the block for 5 seconds
[NSThread sleepForTimeInterval:5.0];
// Call the method if the app is backgrounded (and not just inactive)
if (application.applicationState == UIApplicationStateBackground)
[self performSelector:#selector(test)]; // Or, you could just call [self test]; here
// Tell the system that the task has ended.
if (taskID != UIBackgroundTaskInvalid) {
[[UIApplication sharedApplication] endBackgroundTask:taskID];
}
});

performSelectorInBackground not working when App is minimized

I'm trying to keep the App running on task in background even if the User minimized and started working in other stuff on the ios device, but my method is hit only when the App is active on the screen. Am I missing something obvious here?
-(void) viewDidAppear:(BOOL)animated
{
[self performSelectorInBackground:#selector(RunMethodEvenWhenMinimized) withObject:nil];
}
-(void)RunMethodEvenWhenMinimized
{
while(YES)
{
//My Code
sleep(10);
}
}
The problem is that you seem to be confusing background processing, and NSObject's background thread helper function. You aren't actually specifying that your method should run in the background, rather, that it will run on a thread that isn't the main thread. It's recommended that you use -[UIApplication beginBackgroundTaskWithExpirationHandler:]; to notify the system that your application needs to exceed it's allotted time in the foreground for method execution. Your code will end up looking something like this when all is said and done:
-(void) viewDidAppear:(BOOL)animated
{
UIApplication *app = [UIApplication sharedApplication];
bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
bgTask = UIBackgroundTaskInvalid;
}];
[self RunMethodEvenWhenMinimized];
[app endBackgroundTask:bgTask];
}
-(void)RunMethodEvenWhenMinimized
{
while(YES)
{
//My Code
sleep(10);
}
}

how can I automatically stop all background tasks when app is coming in foreground state

i am using following code
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 40000
if ([[UIApplication sharedApplication]
respondsToSelector:#selector(beginBackgroundTaskWithExpirationHandler:)])
{
UIBackgroundTaskIdentifier bgTask = [[UIApplication sharedApplication]
beginBackgroundTaskWithExpirationHandler:^{}];
// Perform work that should be allowed to continue in background
[self changeCounter];
//[NSThread detachNewThreadSelector:#selector(changeCounter) toTarget:self withObject:_viewController];
[[UIApplication sharedApplication] endBackgroundTask:bgTask];
}
#endif
changeCounter contains loops which may end after some time .But before ending the loop if app comes in foreground then i can see only black screen untill loop finishes.
So how can I stop all tasks as app comes in foreground
this is code for changeCounter
-(void)changeCounter{
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
while(back==1.0f){
NSLog(#"loop is runnig");
[NSThread sleepForTimeInterval:1.0];
if(_viewController.minutes<0){
if(_viewController.fadeSeconds>0){
float div=_viewController.deviceVolume/[_viewController.volumeData.fadeTime floatValue];
_viewController.musicPlayer.volume=_viewController.musicPlayer.volume-div;
_viewController.fadeSeconds-=1;
}
else {
[self stop];
//self.musicPlayer.volume=0.0f;
// counter=0;
NSLog(#"closing the sound");
[_viewController.musicPlayer pause];
NSLog(#"fade seconds %i minutes ",_viewController.fadeSeconds);
if(_viewController.dvol==0){
_viewController.musicPlayer.volume=deviceVolume;
_viewController.dvol=1;
}
back=0.0f;
}// end of the else
}
I think that your [self changeCounter] method is running on your application's main thread which is why your seeing the black screen until the operation is finished. You should think about whether it is appropriate for that operation to run on the main thread or whether you should move it to a background thread. Do you really want to kill the task when you come back to the foreground or is it ok for the task to complete as long as the UI is not locked up?

iPhone background task stops running at some point

In my iPhone app I have a background task that starts running when the app enters the background state.
The task runs fine and is structured in a loop like this:
run.
sleep for 5 min.
run.
sleep for 5 min.
etc.
For some reason, the task stops running after a certain amount of time... say half an hour to an hour.
Can anyone help me understand why?
Here is the background task code:
- (void)applicationDidEnterBackground:(UIApplication *)application {
NSLog(#"Application entered background state.");
if ([[NSUserDefaults standardUserDefaults] boolForKey:#"bgCheckSwitch"] == YES) {
//UIApplication* app = [UIApplication sharedApplication];
// Request permission to run in the background. Provide an
// expiration handler in case the task runs long.
NSAssert(bgTask == UIBackgroundTaskInvalid, nil);
bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
// Synchronize the cleanup call on the main thread in case
// the task actually finishes at around the same time.
dispatch_async(dispatch_get_main_queue(), ^{
if (bgTask != UIBackgroundTaskInvalid)
{
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}
});
}];
// Start the long-running task and return immediately.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Do the work associated with the task.
[someClass doSomeThing]; //The actual method performed by the task. The looping is in the method.
dispatch_async(dispatch_get_main_queue(), ^{
if (bgTask != UIBackgroundTaskInvalid)
{
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}
});
});
}
}
A background task only runs for a period of time, then the OS kills it. This is discussed in the multitasking WWDC10 videos.