Reputation: 2023
From my main thread, I launch an image loader method method-A (below). The problem is, if method-A is not finished at the time a new method-A call is made, image loading starts from the beginning.
What I want to do is, nullify any new method-A calls that are made while a previous method-A call is still doing work... The way I (attempt to) do it now is having a simple global BOOL variable (BOOL imageLoaderBusy) and using it to keep track if the method-A is still working or not (as shown below).
The problem is, the variable seems to be ignored sometimes, and new method-A calls are undesirably started...I dunno. Maybe there is a special way you need to create global variables to make them accessible / valid across multiple threads?
Can somebody please tell me what I am doing wrong? Thanks.
//Method-A called like this:
[self performSelectorInBackground:@selector(loadPagesWithGraphics:) withObject:nil];
//Method-A
-(IBAction)loadPagesWithGraphics:(id)sender{
NSAutoreleasePool *arPool = [[NSAutoreleasePool alloc] init];
if(!imageLoaderBusy){
imageLoaderBusy = YES;
// Load Images
}
imageLoaderBusy = NO;
[arPool release];
}
Thanks in advance.
Upvotes: 2
Views: 3768
Reputation:
Regardless of a variable being an instance variable or a global variable, if multiple threads may write to that variable concurrently, you need to lock that section of code. For instance,
-(IBAction)loadPagesWithGraphics:(id)sender{
@synchronized(self) {
if (imageLoaderBusy) return;
imageLoaderBusy = YES;
}
NSAutoreleasePool *arPool = [[NSAutoreleasePool alloc] init];
// Load Images
imageLoaderBusy = NO;
[arPool release];
}
Let’s say two executions of that method happen simultaneously in threads A and B, and A gets the lock first, so thread B waits for the lock to be released. From A’s perspective, imageLoaderBusy == NO
so it doesn’t return, sets imageLoaderBusy = YES
, and releases the lock.
Since the lock has been released, thread B can start executing. It checks imageLoaderBusy
and, since thread A has set it to YES
, the method returns immediately in thread B.
Thread A proceeds to load the images and sets imageLoaderBusy
to NO
.
Note that this means that if the method is called again in some thread it will be executed and load the images again. I’m not sure if that’s your intended behaviour; if it’s not, you’ll need another check to determine if images have already been loaded. For instance,
-(IBAction)loadPagesWithGraphics:(id)sender{
if (imagesHaveBeenLoaded) return;
@synchronized(self) {
if (imageLoaderBusy) return;
imageLoaderBusy = YES;
}
NSAutoreleasePool *arPool = [[NSAutoreleasePool alloc] init];
// Load Images
[arPool release];
imageLoaderBusy = NO; // not strictly necessary
imagesHaveBeenLoaded = YES;
}
You don’t need to have all the method inside a @synchronize
block. In fact, critical sections should usually be kept small, especially if the lock is being applied to the whole object (self
). If the entire method were a critical section, thread B would have to wait until all images are loaded before noticing that another thread was already busy/had already loaded the images.
Upvotes: 2
Reputation: 16719
Try to change this way:
-(IBAction)loadPagesWithGraphics:(id)sender{
if( imagesDidLoad ) return;
@synchronized(self) {
NSAutoreleasePool *arPool = [[NSAutoreleasePool alloc] init];
// Load Images
[arPool release];
//set global ivar
imagesDidLoad = YES;
}
}
and in Method-A
add
-(void) methodA {
if( !imagesDidLoad )
[self performSelectorInBackground:@selector(loadPagesWithGraphics:) withObject:nil];
}
Upvotes: 1
Reputation: 6650
in Method-a
call a setter on you're main thread to set that BOOL
.
The method to do that is : - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait
Upvotes: 1