dispatch_queue_t still blocking main thread

16,408

Solution 1

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.

Solution 2

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?

Solution 3

To my understanding, ASIHTTPRequest startSynchronous requires NSRunLoop, and GCD serial queue thread doesn't have NSRunLoop. Thus, it doesn't work.

And your code seems that GCD serial queue is not needed at all. I think GCD global queue works fine with your code. For example,

- (void)getNewImages
{
    dispatch_queue_t queue =
        dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);

    /* snip */
    for (Manufacturer *m in self.manufacturers)
    {
        /* snip */
        ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
        [request setCompletionBlock:^{
            /* snip */
            dispatch_async(queue, ^{
                [responseString writeToFile:fileName atomically:YES encoding:NSUTF8StringEncoding error:nil];
            });

            for (NSDictionary* dict in array) {
                /* snip */
                ASIHTTPRequest *imageRequest = [ASIHTTPRequest requestWithURL:url];
                [imageRequest setCompletionBlock:^{
                    /* snip */
                    dispatch_async(queue, ^{
                        [responseData writeToFile:[savePath stringByReplacingOccurrencesOfString:@"%20" withString:@" "] atomically:YES];
                    });
                }];
                /* snip */
                [imageRequest startAsynchronous];
            }
            /* snip */
        }];
        /* snip */
        [request startAsynchronous];
    }
}

getNewImages method should be executed on the main thread, it requires the NSRunLoop of the main thread.

Share:
16,408
Slee
Author by

Slee

Updated on August 09, 2022

Comments

  • Slee
    Slee almost 2 years

    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.

  • Slee
    Slee almost 13 years
    how am I suppose to know when this queue is done? Do I need to create some sort of delegate that gets called after the queue is finished? That seems completely counter intuitive as to the whole point of queues.
  • RyanR
    RyanR almost 13 years
    Updated answer. How is the system supposed to know when the queue is done better than you? You can add more tasks to a queue at any time, so you need to release the resources used by that queue when it's appropriate. I strongly encourage you to read the linked Concurrency guide. Twice. GCD makes it really easy to shoot yourself in both feet.
  • RyanR
    RyanR almost 13 years
    @Slee, I might be wrong about what was causing your UI to block. Try the updated answer with the release chained to the end of queue operations, and if it still blocks your UI please share how getNewImages is implemented, as that is the only rock I haven't looked under.
  • Slee
    Slee almost 13 years
    it' actually working great on my created serial queue, I just had to put an NSAutoreleasePool around my ASIHTTPRequests and made them syncronous for the image download part. Memory stays at round 3~4 megs for the entire time.
  • user102008
    user102008 about 11 years
    "Second, you are immediately trying to release the queue you just created (and is presumably doing some work). Don't do that." Why not? dispatch_async retains the passed queue for the duration of the dispatch.