For a game I'm developing, I call an expensive method from one of the touch processing routines. In order to make it faster, I decided to use performSelectorInBackgroundThread, so instead of:
[gameModel processPendingNotifications];
I switched to:
[gameModel performSelectorInBackground:#selector(processPendingNotifications) withObject:nil];
The first problem I had, is that processPendingNotifications did not have a NSRunLoop, so I added it, like this:
- (void)processPendingNotifications {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[pendingNotificationsQueue makeObjectsPerformSelector:#selector(main)];
[pendingNotificationsQueue removeAllObjects];
[pool drain];
}
So far so good. My problem is that some of the methods that are called from this background thread create new NSTimer instances. These instances, end up not firing. I think this is because the secondary thread I have doesn't have a (or is ending its) NSRunLoop. I'm starting the new timers by using:
[NSTimer scheduledTimerWithTimeInterval:20.0 target:self selector:#selector(timerFired) userInfo:nil repeats:NO];
My questions are:
Am I on the right path of suspecting the problem has to do with the NSRunLoop ?
Is there a way I can start a NSTimer from a background thread and attach it to the main thread's NSRunLoop ?
Yes, you need a runloop to dispatch the timer events
NSTimers are implicitly attached to the runloop of the thread they are created on, so if you want it to be attached to the main thread's runloop create it on the main thread using performSelectorOnMainThread:withObject:waitUntilDone:. Of course that code will execute on the main thread.
If your question is "Can I have a timer run on the main thread's run loop that directly runs a selector on another thread?" The answer is no, but the selector it fires on the main thread could just performSelector:onThread:withObject:waitUntilDone:. Of course that requires the thread you are trying to perform the selector against have an operational runloop.
If you want the code the timer is triggerring running on a background thread then you really need to get the background thread's runloop going, and if you do that then you don't need to schedule anything with the main thread since you will have an operational runloop.
The NSTimers actually just periodically fire events into the enclosing NSRunLoop, which each thread has (or should have). So, if you have a child (or background) process running in a different thread, the NSTimers will fire against that thread's NSRunLoop instead of the application's main NSRunLoop.
You could ensure that timers are always created against the main runloop by sending it the addTimer:forMode: message with your newly instantiated (but not started) NSTimer. Accessing the main application's run loop is done with [NSRunLoop mainRunLoop], so regardless of which thread you're in, doing [[NSRunLoop mainRunLoop] addTimer:[NSTimer timerWithTimeInterval:20.0 target:self selector:#selector(timerFired) userInfo:nil repeats:NO]] forMode:NSDefaultRunLoopMode] will always schedule the timer against the main runloop.
However, bear in mind that the execution is not guaranteed at that interval, and if your main loop is busy doing something your timer will be left waiting until it's ready.
It's worth considering NSOperation and NSOperationQueue if you really want background activity to occur.
Related
When I cancel an NSOperation (when user presses a button) cancel method is called from the main thread, but evidently the operation is running in another thread.
So, to avoid race conditions when I change _isExecuting and _isFinished, I think cancel (or at least its logic) should be called from the same thread that the NSOperation. Apart from that, when user cancels it, several files are deleted and it takes time. Because cancel is called from main thread, all the app becomes unresponsive for a while, which is ugly.
How can I execute cancel code in the same thread that the current NSOperation?
I tried this in cancel (similar to what I saw in ASIHTTPRequest):
if (_operationThread) {
[self performSelector:#selector(cancelOnRequestThread) onThread:_operationThread withObject:nil waitUntilDone:NO];
} else {
[self cancelOnRequestThread];
}
And _operationThread is setted in start method using:
_operationThread=[NSThread currentThread];
But it doesn't work.
Any idea or suggestion?
Note: I use concurrent operations, so I use start instead of main.
Thanks a lot for help.
Ricardo.
It's fine to call cancel on an NSOperation from the main thread. The cancel method is thread-safe.
That shouldn't cause any blocking on your main thread because the cancel method itself shouldn't be doing any work. If you have overridden the cancel method of your operation to delete files, etc then that is the wrong approach. You shouldn't override the cancel method, instead just check the isCancelled method at regular points within the operation's main method (e.g. inside any tight loops) and then return from main early if isCancelled returns YES, which will then cancel the operation on the same thread as the rest of the execution.
If that's how you've implemented it already and you're still having performance issues, is it possible that your operation is not really running on a background thread at all? For example if you've added it to the queue returned by [NSOperationQueue mainQueue] then that's actually running on the main application thread.
I cant seem to find a suitable answer on SO or google.
I am hitting a web server for new data every 15 seconds. The problem is while waiting for a response from the server (a few seconds at times) my user interface is tied up. I cant seem to figure out how to start an NSTimer on a seperate thread so I can pull new data in the background, leaving my UI buttons responsive.
Any suggestions?
Thanks!
When you request from the server, you can use an asynchronous NSURLRequest to receive the data. Then, you could keep your NSTimer in the main thread, because all it would be doing is creating an asynchronous NSURLRequest every 15 seconds.
Otherwise you can create a NSTimer in the background thread by creating the timer with
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)seconds target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)repeats
Then add the timer to whatever run loop you want by calling
[[NSRunLoop currentRunLoop] addTimer:MY_TIMER forMode:NSDefaultRunLoopMode];
(Replace currentRunLoop with the run loop that you want the NSTimer to be in.)
There are various ways of working with threads, but GCD (Grand Central Dispatch) is probably the easiest.
Here is a tutorial example building a simple job queue in GCD.
I am using a thread to update messages in background in my application. The thread is started in my messages class.
Messages.m
timerThread = [[NSThread alloc] initWithTarget:self
selector:#selector(startTimerThread:) object:nil];
[timerThread start];
now I want the thread to stop when the user signout of the application. For that in the signout method (in another class) I added
Messages *msg=[[Messages alloc]init];
[msg.timerThread cancel];
[msg release];
But even after signing out of the application the thread is still running.
[timerThread cancel] doesn't stop thread, it just marks it as cancelled. I presume that your startTimerThread: performs some sort of infinite loop. You have to check isCancelled in this loop periodically and if it's YES you should perform some clean up and break from this loop.
BTW if you don't perform updates frequently it is more convenient to run NSTimer in main thread and detach new thread on callbacks (like [self performSelectorInBackground:#selector(updateMessages:) withObject:whatever]) or start an instance of NSOperation.
( I am assuming here that you create an instance of Messages somewhere else in the program )
According to your provided code you are creating a new Messages instance as part of your sign out process ( which starts a new thread ), canceling that new thread ( -cancel is a proper way to stop a thread -- if your thread method follows the considerations listed in the documentation ), then releasing your ( extra? ) Messages instance. At this point your original Messages instance is still around, and the thread from that is still running.
If you want the thread to stop when the instance of the class is deallocated, you probably should take care of that in a -dealloc method on Messages and make sure your original Messages instance is released at the proper time.
[NSThread exit];
Have you checked this method of NSThread class.
Use [NSThread exit]; or [NSThread cancel];
In my iPhone app, I have to perform function constantly in background.
For that I think I will have to use NSThread to call the function and keep it executing in background.
I dont want to stall my app and hence I want to use NSThread to keep my Main Thread free for user interaction.
How should I implement NSThread to perform the function in background?
EDIT:
The function is for fetching the data from a web server every 20 seconds and updating the tables in my iPhone app based on the data that is fetched from the web server.
I'd look at an NSOperationQueue first.
I'm guessing that your background task is really a small task repeated again and again. Make this into an NSOperation subclass and just add them onto an NSOperationQueue. That way you can control the background tasks more easily.
You also get the advantage with an NSOperationQueue that when there are no operations to run, the processor isn't just stuck in a while(YES) loop, waiting. This will help your app's UI be more responsive and will help battery life :)
However, if your background task is a single long running task that just needs to be started and then ignored, performSelectorInBackground isn't too bad an idea.
Sounds like a bad idea, but it's very simple.
[self performSelectorInBackground:#selector(theMethod:) withObject:nil];
Just have a while(YES) in theMethod: and it will never stop executing.
EDIT:
Luckily for you it's just as simple to do something once every 20 seconds.
[NSTimer scheduledTimerWithTimeInterval:20 target:self selector:#selector(theMethod:) userInfo:nil repeats:YES];
This will execute theMethod: once every 20 seconds. I might also add that this is a much better idea.
you'll want to interact with the thread's run loop. if it's an NSThread, it is created automatically. thus, you are given access to the CF/NS-RunLoop - typically by calling + [NSThread currentRunLoop] from the secondary thread.
in that case, you add a CF/NS-Timer to the run loop, and let it run and repeat until your work is finished. when the timer fires, your thread is awoken, and you then do your work.
I am a seemingly straightforward question that I can't seem to find an answer to (and it is hindering my app).
I have a background thread running a paricular method:
-(void)processImage:(UIImage *)image {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
//Process image here in the background here
[pool drain];
}
This much works great, but my question comes when I want to call another method from inside the already-background method. Does this call stay in the background? Do I need to add NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; and [pool drain]; to the new method to make it run in the background as well?
Any advice would be very helpful. I am a bit confused about this.
Many thanks,
Brett
It WILL stay in the background, on the same thread it was called from.
Some threading notes to consider with this:
It may not be obvious, but if you call a timer from the background thread, and the thread exits before the timer is supposed to go off, the timer will NOT be called. Thus it is recommended you setup timers from the main thread
You dont need another autorelease pool unless you spawn another thread.
Any UI updates should be done on the main thread
You don't need to add yet another autorelease pool, the one you already have is enough. And yes, all calls that you make that originate from that thread stay in that thread and thus also run "in the background". Exception would be the use of "performSelectorOnMainThread:", which of course makes the given selector to be performed on the main thread :-) If you want to call GUI methods (like setting the image on an UIImageView) you should make sure to do so on the main thread. See the docs for "performSelectorOnMainThread:waitUntilDone:" (sorry for not giving you you the links, am typing this on my iPad).