Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

dispatch_queue_t still blocking main thread

I want to fire off a method and have it run in the background - I do not care what really happens to it after it is started.

So in my main viewDidLoadMethod I have all of my usually code and this:

 dispatch_queue_t newImages = dispatch_queue_create("load image in background", NULL);
    dispatch_async(newImages, ^{
        [self getNewImages];
    });
    dispatch_release(newImages);

My assumption was that the queue would be created and then that function call would be set to run in a background thread and my app would keep cruising along. That does not seem to be the case.

Is everything that is called from that function automatically moved to the background thread or do I now need to make sure there are no other possible blocking calls happening.

EDIT - CODE THAT IS BLOCKING:

-(void) getNewImages
{
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsPath = [paths objectAtIndex:0];
    NSString *lastImagesSyncDate = [defaults valueForKey:@"ImagesLastSyncDate"];

    dispatch_queue_t newImages = dispatch_queue_create("com.mydomain.app.newimagesinbackground", NULL);
    dispatch_async(newImages, ^{

        for (Manufacturer *m in self.manufacturers)
        {
            NSString *myurl = [NSString stringWithFormat: kNewImagesURL, m.ManufacturerID  ,lastImagesSyncDate];
            NSString *manufacturerID = [m.ManufacturerID stringValue];
            NSURL *url = [NSURL URLWithString:[myurl stringByReplacingOccurrencesOfString:@" " withString:@"%20"]];
            __block ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
            [request setCompletionBlock:^{

                // Use when fetching text data
                NSString *responseString = [request responseString];
                NSString *fileName =[documentsPath stringByAppendingPathComponent: [NSString stringWithFormat:@"newimages%@.plist", manufacturerID]];
                [responseString writeToFile:fileName atomically:YES encoding:NSUTF8StringEncoding error:nil];
                NSArray *array = [[NSArray alloc] initWithContentsOfFile:fileName];

                for (NSDictionary* dict in array) {

                    NSString *fileName = [NSString stringWithFormat:@"%@/%@_tn.jpg?t=",manufacturerID, [[dict valueForKey:@"ItemID"]  stringByReplacingOccurrencesOfString:@" " withString:@"%20"]];
                    NSString *savePath = [documentsPath stringByAppendingPathComponent:fileName];
                    NSURL *url = [NSURL URLWithString: [[NSString stringWithFormat:kProductImagesURL, fileName]stringByAppendingString:lastImagesSyncDate]];

                    __block ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
                    [request setCompletionBlock:^{

                        int statusCode = [request responseStatusCode];
                        if (statusCode==200) {
                            NSData *responseData = [request responseData];
                            [responseData writeToFile:[savePath stringByReplacingOccurrencesOfString:@"%20" withString:@" "] atomically:YES];
                        }  

                    }];
                    [request setFailedBlock:^{
                        //  NSError *error = [request error];
                    }];
                    [request startAsynchronous];  
                }

                [array release];     
            }];
            [request setFailedBlock:^{
                //  NSError *error = [request error];
            }];
            [request startAsynchronous];
        }

        dispatch_async(dispatch_get_main_queue(), ^{
            dispatch_release(newImages); //this executes on main thread
        });
    });
}

I would assume that there is only 1 queue created here with all of these calls added to it running 1 at a time until they are complete. I tried several variations of this and it drags my app to a grinding halt - it doesn't crash but the main thread is crawling.

UPDATE: So it finally dawned on me that having all of those async ASIHTTPRequest inside my queue was creating a massive amount of threads, so updated my code to just have the queue add the long running part of this code to the queue and use a synchronous request instead:

 for (NSDictionary* dict in array) {
                dispatch_async(newImages, ^{  
                    NSString *fileName = [NSString stringWithFormat:@"%@/%@_tn.jpg?t=",manufacturerID, [[dict valueForKey:@"ItemID"]  stringByReplacingOccurrencesOfString:@" " withString:@"%20"]];
                    NSString *savePath = [documentsPath stringByAppendingPathComponent:fileName];
                    NSURL *url = [NSURL URLWithString: [[NSString stringWithFormat:kProductImagesURL, fileName]stringByAppendingString:lastImagesSyncDate]];

                    ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
                    [request startSynchronous];
                    NSError *error = [request error];
                    if (!error) {
                        int statusCode = [request responseStatusCode];
                        if (statusCode==200) {
                            NSData *responseData = [request responseData];
                            [responseData writeToFile:[savePath stringByReplacingOccurrencesOfString:@"%20" withString:@" "] atomically:YES];
                        }  
                    }
                });
}

NOW I just need to figure out where to release the queue again, I have tried several variations with no luck.

like image 314
Slee Avatar asked Jun 27 '11 19:06

Slee


2 Answers

First, you should name the queue using reverse domain syntax, eg "com.foo.myapp.backgroundwork" - this ensures uniqueness and consistency.

Second, you are immediately trying to release the queue you just created (and is presumably doing some work). Don't do that. Release the queue after work is done instead. The following is actually documented in the Concurrency programming guide, under "managing queue memory"

dispatch_async(newImages, ^{
        [self getNewImages];
        dispatch_async(dispatch_get_main_queue(), ^{
          dispatch_release(newImages); //this executes on main thread
        });
    });

edit: After re-reading the concurrency guide myself (I take my own medicine), I learned that the runtime will actually clean up a dispatch_queue that reaches 0 reference count on a background thread, so that shouldn't block your UI. It's still a bad idea to release it immediately, and instead follow the practice I demonstrated, where your task queues up it's cleanup on the main thread. So, in hindsight I'm thinking that your -getNewImages method might be responsible for blocking your UI. Can you show some code from that method so we can rule that out?

like image 196
RyanR Avatar answered Oct 11 '22 08:10

RyanR


I went the long way around but finally got the concept in my head and here is what is working perfectly for me:

dispatch_queue_t newImages = dispatch_queue_create("com.mydomain.app.newimagesinbackground", NULL); // create my serial queue
        dispatch_async(newImages, ^{
     [self getNewImages]; // call my function - this get added first in my serial queue
    });


    dispatch_async(newImages, ^{
        dispatch_async(dispatch_get_main_queue(), ^{
            // add this to the main queue as the last item in my serial queue
            // when I get to this point I know everything in my queue has been run
            dispatch_release(newImages);
        });
    });

My main problem was using the ASIHTTPRequest startAsynchronous method: http://allseeing-i.com/ASIHTTPRequest/How-to-use#using_blocks

That in essence was creating my bottleneck, 2000 images to get, 2000 asynchronous calls trying to be made. If I am already in the background the startSynchronous method works just fine and only 1 call is trying to run at a time.

like image 32
Slee Avatar answered Oct 11 '22 09:10

Slee