Calling performSelector:onThread:withObject:waitUntilDone: more than once on same thread results in delay - objective-c

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.

Related

Obj-C return to a block from a delegate method?

I'm writing a mac app that runs its own web server, using the GCDWebServer library (https://github.com/swisspol/GCDWebServer). My app delegate handles GET requests like so:
__weak typeof(self) weakSelf = self;
[webServer addDefaultHandlerForMethod:#"GET"
requestClass:[GCDWebServerRequest class]
processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
return [weakSelf handleRequest:request];
}];
And then the handleRequest method returns the response data, something like:
return [GCDWebServerDataResponse responseWithHTML:#"<html><body><p>Hello World!</p></body></html>"];
So far so good. Except now I want the handleRequest method to use NSSpeechSynthesizer to create an audio file with some spoken text in it, and then wait for the speechSynthesizer:didFinishSpeaking method to be called before returning to the processBlock.
// NSSpeechSynthesizerDelegate method:
- (void)speechSynthesizer:(NSSpeechSynthesizer *)sender didFinishSpeaking:(BOOL)success
{
NSLog(#"did finish speaking, success: %d", success);
// return to processBlock...
}
Problem is, I have no idea how to do this. Is there a way to return from the speechSynthesizer:didFinishSpeaking method into the processBlock defined above?
You need to run the speech synthesizer on a separate thread with its own run loop, and use a lock to allow your request thread to wait for the operation to complete on the speech thread.
Assuming the web server maintains its own thread(s) and runloop, you can use your app's main thread to run the speech synthesizer, and you can use NSCondition to signal completion to the web response thread.
A basic (untested) example (without error handling):
#interface SynchroSpeaker : NSObject<NSSpeechSynthesizerDelegate>
- (id)initWithText:(NSString*)text outputUrl:(NSURL*)url;
- (void)run;
#end
#implementation SynchroSpeaker
{
NSCondition* _lock;
NSString* _text;
NSURL* _url;
NSSpeechSynthesizer* _synth;
}
- (id)initWithText:(NSString*)text outputUrl:(NSURL*)url
{
if (self = [super init])
{
_text = text;
_url = url;
_lock = [NSCondition new];
}
return self;
}
- (void)run
{
NSAssert(![NSThread isMainThread], #"This method cannot execute on the main thread.");
[_lock lock];
[self performSelectorOnMainThread:#selector(startOnMainThread) withObject:nil waitUntilDone:NO];
[_lock wait];
[_lock unlock];
}
- (void)startOnMainThread
{
NSAssert([NSThread isMainThread], #"This method must execute on the main thread.");
[_lock lock];
//
// Set up your speech synethsizer and start speaking
//
}
- (void)speechSynthesizer:(NSSpeechSynthesizer *)sender didFinishSpeaking:(BOOL)success
{
//
// Signal waiting thread that speaking has completed
//
[_lock signal];
[_lock unlock];
}
#end
It's used like so:
- (id)handleRequest:(id)request
{
SynchroSpeaker* speaker = [[SynchroSpeaker alloc] initWithText:#"Hello World" outputUrl:[NSURL fileURLWithPath:#"/tmp/foo.dat"]];
[speaker run];
////
return response;
}
GCDWebServer does run into its own threads (I guess 2 of them) - not in the main one. My solution needed to run code in Main Thread when calling the ProcessBlock.
I found this way that suits my needs:
First declare a weak storage for my AppDelegate: __weak AppDelegate *weakSelf = self;. Doing so I can access all my properties within the block.
Declare a strong reference to AppDelegate from within the block like so: __strong AppDelegate* strongSelf = weakSelf;
Use NSOperationQueue to align the operation on mainThread:
[[NSOperationQueue mainQueue] addOperationWithBlock:^ {
//Your code goes in here
NSLog(#"Main Thread Code");
[strongSelf myMethodOnMainThread];
}];
In this way myMethodOnMainThread surely will run where it's supposed to.
For sake of clarity I quote my relevant code section:
webServer = [[GCDWebServer alloc] init];
webServer.delegate = self;
__weak AppDelegate *weakSelf = self;
// Add a handler to respond to GET requests
[webServer addDefaultHandlerForMethod:#"GET"
requestClass:[GCDWebServerRequest class]
asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) {
__strong AppDelegate* strongSelf = weakSelf;
[[NSOperationQueue mainQueue] addOperationWithBlock:^ {
//Your code goes in here
NSLog(#"Main Thread Code");
[strongSelf myMethodOnMainThread];
}];
GCDWebServerDataResponse* response = [GCDWebServerDataResponse responseWithJSONObject:packet];
completionBlock(response);
}];
GCWebServer supports fully asynchronous responses as of version 3.0 and later [1].
[webServer addDefaultHandlerForMethod:#"GET"
requestClass:[GCDWebServerRequest class]
asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) {
// 1. Trigger speech synthesizer on main thread (or whatever thread it has to run on) and save "completionBlock"
// 2. Have the delegate from the speech synthesizer call "completionBlock" when done passing an appropriate response
}];
[1] https://github.com/swisspol/GCDWebServer#asynchronous-http-responses

NSThread Not Loading Selector Method

In the initialization method of a class I am declaring the thread as such:
NSThread* myThread = [[[NSThread alloc] initWithTarget:self selector:#selector(m_run_thread) object:nil] autorelease];
[myThread start];
I also have a boolean value which is set to NO. Later on in the code I set the boolean value to YES.
bool_run_progress_thread = YES;
The contents of the method m_run_thread is as follows:
-(void) m_run_thread
{
if (bool_run_progress_thread)
{
//do processing here
}
bool_run_progress_thread = NO;
}
The problem is that the method m_run_thread is never being accessed. What am I doing wrong?
P.S. I have also tried to set up the Thread using the following (and older)method:
[NSThread detachNewThreadSelector:#selector(m_run_thread)
toTarget:self
withObject:nil];
... but to no avail as well.
"...and I am only getting it to show once" Yes, that's exactly how it should be. After being started, a thread runs once from its start to its end (ignoring errors here for the moment), and having reached the end, the thread is essentially dead and gone.
If you want the thread to repeat its execution, you have to prepare for that yourself:
- (void) m_run_thread
{
for (;;)
{
if (bool_run_progress_thread)
{
//do processing here
bool_run_progress_thread = NO;
}
}
}
But there is still a lot wrong with this code: essentially, when run, the code forms a busy waiting loop. Assuming, that bool_run_progress_thread is only ever true for short periods of time, the background thread should be sleeping most of the time. Insead, if you try the code as its stands, it will instead consume CPU time (and lots of it).
A better approach to this would involve condition variables:
#class Whatsoever
{
NSCondition* cvar;
BOOL doProgress;
...
}
...
#end
and
- (void) m_run_thread
{
for (;;)
{
[cvar lock];
while (!doProgress)
{
[cvar wait];
}
doProgress = NO;
[cvar unlock];
... do work here ...
}
}
and in order to trigger the execution, you'd do:
- (void) startProgress
{
[cvar lock];
doProgress = YES;
[cvar signal];
[cvar unlock];
}
Doing things this way also takes care of another subtle problem: the visibility of the changes made to the global flag (your bool_run_progress_thread, my doProgess). Depending on the processor and its memory order, changes made without special protection might or might not become (ever) visible to other threads. This problem is taken care of by the NSCondition, too.

How to pause/continue NSThread

I have an app, where i use function FSMoveObjectToTrashSync. It works in background thread. I need ability for my app, to click on button to pause it or continue(if it paused) how i can make it?
Example of code:
NSMutableArray *fileArray = [NSMutableArray array withobjects:#"file1url", #"file2", #"file3", nil];
NSMutableArray *threadArray = [[NSMutableArray alloc] init];
-(void)myFunc{
for (NSURL *url in fileArray){
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:#selector(mySelectorWith:) object:url];
[thread start];
[threadArray addObject:thread];
}
}
-(void)mySelectorWith:(NSURL *) url{
FSRef source;
FSPathMakeRef((const UInt8 *)[[url path] fileSystemRepresentation], &source, NULL);
FSMoveObjectToTrashSync(&source, NULL, kFSFileOperationDefaultOptions);
}
PS:sorry for my english, i'm from Belarus... =(
One solution would be to replace the for loop on a single thread with an NSOperation subclass. Each operation should trash exactly one object; you then create one operation for each object you want to trash and put all of the operations on an NSOperationQueue.
The operation queue will run each operation on a thread, and it can even run multiple operations on multiple threads if it sees enough computing power laying around to do it.
An operation queue can be paused and resumed at will; when you suspend the queue, any operations in that queue that are already running will finish, but no more will start until you resume the queue.
You could use an NSConditionLock. An NSConditionLock is similar to a condition variable. It has a couple of basic methods, lockWhenCondition, and unlockWithCondition, and lock. A typical usage is to have your background thread waiting on the condition lock with "lockWhenCondition:", and the in you foreground thread to set the condition, which causes the background thread to wake up. The condition is a simple integer, usually an enumeration.
Here's an example:
enum {
kWorkTodo = 1,
kNoWorkTodo = 0
}
- (id)init {
if ((self = [super init])) {
theConditionLock = [[NSConditionLock alloc] initWithCondition: kNoWorkTodo];
workItems = [[NSMutableArray alloc] init];
}
}
- (void)startDoingWork {
[NSThread detachNewThreadSelector:#selector(doBackgroundWork) toTarget:self withObject:nil];
}
- (void)doBackgroundWork:(id)arg {
while (YES) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSArray *items = nil;
[theConditionLock lockWhenCondition:kWorkTodo]; // Wait until there is work to do
items = [NSArray arrayWithArray:workItems]
[workItems removeAllObjects];
[theConditionLock unlockWithCondition:kNoWorkTodo];
for(id item in items) {
// Do some work on item.
}
[pool drain];
}
}
- (void)notifyBackgroundThreadAboutNewWork {
[theConditionLock lock];
[workItems addObject:/* some unit of work */];
[theConditionLock unlockWithCondition:kWorkTodo];
}
In this example, when startDoingWork is called doBackgroundWork: will start on a background thread, but then stop because there isn't any work to do. Once notifyBackgroundThreadAboutNewWork is called, then doBackgroundWork: will fire up and process the new work, and then go back to sleep waiting for new work to be available, which will happen the next time notifyBackgroundThreadAboutNewWork is called.

Objective-C Threading: Exiting a thread, retain problems

I'm trying to create a thread that configures a run loop to run a physics engine through a defined NSTimer. However, I'm having trouble making the thread exit normally (or I think the problem is).
Attached are the relevant portions of my code:
(This code is in a view controller)
(back is called when a button is pressed)
- (void)back {
[timestep invalidate];
exiting = YES;
[self release];
}
- (void)initializePhysicsWorld {
// Initializes the thread to simulate physics interactions.
[NSThread detachNewThreadSelector:#selector(physicsThreadMethod)
toTarget:self
withObject:nil];
}
- (void)physicsThreadMethod {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSRunLoop *myRunLoop = [NSRunLoop currentRunLoop];
timestep = [NSTimer timerWithTimeInterval:1.0f/60.0f
target:self
selector:#selector(step:)
userInfo:nil
repeats:YES];
[myRunLoop addTimer:timestep forMode:NSDefaultRunLoopMode];
while (!exiting) {
CFRunLoopRun();
[pool release];
pool = [[NSAutoreleasePool alloc] init]; // periodically refreshes pool
}
CFRunLoopStop([myRunLoop getCFRunLoop]);
NSLog(#"Thread is going to exit");
[pool release];
}
- (void)dealloc {
if ([self.view superview]) {
[self.view removeFromSuperview];
}
[super dealloc];
}
The engine (the step: function) runs fine, but when I try to exit the loop by running the method back, it would appear that the thread does not release its retain on my view controller (dealloc is not called). I think my thread didn't exit the physicsThreadMethod method as the NSLog does not appear in the console. Dealloc was only called when I run 'back' a second time.
I'm not really sure why this is happening, so I would really appreciate any help. Thanks!
The problem lies in here:
while (!exiting) {
CFRunLoopRun(); //<-- here you start the run loop.
// the lines under this line are NEVER executed. also the while loop does nothing
[pool release];
pool = [[NSAutoreleasePool alloc] init]; // periodically refreshes pool
}
You could use NSOperations to wrap your work, there are already properties defined on this class to do exactly this kind of thing.
If you want to stick with your implementation in a NSThread you have to take a look at the CFRunLoop reference and how to add observers to the run loop.
Does CFRunLoopRun return every step:? CFRunLoopStop or [myRunLoop runUntilDate:] would help.

Receiving memory warning when using performSelectorInBackground

I have a UITableView that, when items are selected, loads a viewController, which inside it performs some operations in the background using performSelectorInBackground.
Everything works fine if you slowly tap items in the tableView (essentially allowing the operations preforming in background to finish). But when you select the items quickly, the app quickly returns some memory warnings until it crashes, usually after about 7 or 8 "taps" or selections.
Any idea why this would be? When I move my code from the background thread to the main thread, everything works fine as well. You just can't make the tableView selections as quickly because it's waiting for the operations to finish.
Code snippets:
//this is called from - (void)tableView:(UITableView *)aTableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
-(void) showLeaseView:(NSMutableDictionary *)selLease
{
LeaseDetailController *leaseController = [[LeaseDetailController alloc] initWithNibName:#"LeaseDetail" bundle:nil];
leaseController.lease = selLease;
//[leaseController loadData];
[detailNavController pushViewController:leaseController animated:NO];
[leaseController release];
}
//this is in LeaseDetailController
- (void)viewDidLoad {
[self performSelectorInBackground:#selector(getOptions) withObject:nil];
[super viewDidLoad];
}
-(void) getOptions
{
NSAutoreleasePool *apool = [[NSAutoreleasePool alloc] init];
NSArray *arrayOnDisk = [[NSArray alloc] initWithContentsOfFile:[appdel.settingsDir stringByAppendingPathComponent:#"optionData"]];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(LEASE_ID contains[cd] %#)", [lease leaseId]];
self.options = [NSMutableArray arrayWithArray:[arrayOnDisk filteredArrayUsingPredicate:predicate]];
[arrayOnDisk release];
[apool release];
}
Every time you perform the getOptions selector in the background, what's really happening is a new thread is being created on your behalf, and the work is being done there. When the user taps your table cells a bunch of times in a row, a new thread is created each time to handle the work. If the work done by getOptions takes some time to complete, you will have multiple threads calling getOptions at the same time. That is to say, the system doesn't cancel previous requests to perform getOptions in the background.
If you assume that it takes N bytes of memory to perform the work done by getOptions, then if the user taps on five table cells in a row and getOptions doesn't finish right away, then you'll find that your app is using 5 * N bytes at that point. In contrast, when you change your app to call getOptions on the main thread, it has to wait for each call to getOptions to complete before it can call getOptions again. Thus when you do your work on the main thread you don't run into the situation where you're using 5 * N bytes of memory to do the work of five instances of getOptions simultaneously.
That's why you run out of memory when you do this work in the background and the user taps multiple table cells: you're doing multiple instances of the work, and each instance requires its own amount of memory, and when they all get added up, it's more than the system can spare.
It looks like you're just calling getOptions once when the user selects a table cell and navigates into a new view controller. Since the user will only be looking at one of these view controllers at a time, you don't really need to have multiple instances of getOptions going on simultaneously in the background. Instead, you want to cancel the previously-running instance before starting the new one. You can do this using an NSOperationQueue, like so:
- (NSOperationQueue *)operationQueue
{
static NSOperationQueue * queue = nil;
if (!queue) {
// Set up a singleton operation queue that only runs one operation at a time.
queue = [[NSOperationQueue alloc] init];
[queue setMaxConcurrentOperationCount:1];
}
return queue;
}
//this is called from - (void)tableView:(UITableView *)aTableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
-(void) showLeaseView:(NSMutableDictionary *)selLease
{
LeaseDetailController *leaseController = [[LeaseDetailController alloc] initWithNibName:#"LeaseDetail" bundle:nil];
leaseController.lease = selLease;
// Cancel any pending operations. They'll be discarded from the queue if they haven't begun yet.
// The currently-running operation will have to finish before the next one can start.
NSOperationQueue * queue = [self operationQueue];
[queue cancelAllOperations];
// Note that you'll need to add a property called operationQueue of type NSOperationQueue * to your LeaseDetailController class.
leaseController.operationQueue = queue;
//[leaseController loadData];
[detailNavController pushViewController:leaseController animated:NO];
[leaseController release];
}
//this is in LeaseDetailController
- (void)viewDidLoad {
// Now we use the operation queue given to us in -showLeaseView:, above, to run our operation in the background.
// Using the block version of the API for simplicity.
[queue addOperationWithBlock:^{
[self getOptions];
}];
[super viewDidLoad];
}
-(void) getOptions
{
NSAutoreleasePool *apool = [[NSAutoreleasePool alloc] init];
NSArray *arrayOnDisk = [[NSArray alloc] initWithContentsOfFile:[appdel.settingsDir stringByAppendingPathComponent:#"optionData"]];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(LEASE_ID contains[cd] %#)", [lease leaseId]];
NSMutableArray * resultsArray = [NSMutableArray arrayWithArray:[arrayOnDisk filteredArrayUsingPredicate:predicate]];
// Now that the work is done, pass the results back to ourselves, but do so on the main queue, which is equivalent to the main thread.
// This ensures that any UI work we may do in the setter for the options property is done on the right thread.
dispatch_async(dispatch_queue_get_main(), ^{
self.options = resultsArray;
});
[arrayOnDisk release];
[apool release];
}