dispatch_queue_t仍然阻塞主线程

时间:2011-06-27 19:15:14

标签: iphone objective-c multithreading ios

我想启动一个方法并让它在后台运行 - 我不关心它启动后到底发生了什么。

所以在我的主viewDidLoadMethod我有我所有的通常代码和这个:

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

我的假设是队列将被创建,然后该函数调用将设置为在后台线程中运行,我的应用程序将继续巡航。情况似乎并非如此。

从该函数调用的所有内容是否自动移动到后台线程,或者我现在需要确保没有其他可能的阻塞调用发生。

编辑 - 阻止的代码:

-(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
        });
    });
}

我认为这里只创建了一个队列,所有这些调用都添加到它一次运行1,直到它们完成。我尝试了几种变体,它将我的应用程序拖动到暂停 - 它不会崩溃,但主线程正在爬行。

更新: 所以我终于意识到在我的队列中拥有所有这些异步ASIHTTPRequest创建了大量的线程,所以更新我的代码只是让队列将这段代码的长时间运行部分添加到队列中并使用同步请求代替:

 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];
                        }  
                    }
                });
}

现在我只需要找出再次释放队列的位置,我已经尝试了几种变化而没有运气。

3 个答案:

答案 0 :(得分:5)

首先,您应该将队列命名为using reverse domain syntax,例如“com.foo.myapp.backgroundwork” - 这可以确保唯一性和一致性。

其次,您正在尝试释放刚刚创建的队列(并且可能正在做一些工作)。不要那样做。改为完成工作后释放队列。以下实际记录在Concurrency programming guide的“管理队列内存”

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

编辑在我自己重新阅读并发指南之后(我自己动用药),我了解到运行时实际上会清理一个在后台线程上达到0引用计数的dispatch_queue,所以不应该阻止你的用户界面。立即发布它仍然是一个坏主意,而是遵循我演示的实践,你的任务在主线程上清理它的清理。因此,事后我认为您的-getNewImages方法可能会阻止您的UI。你能从这种方法中显示一些代码,以便我们可以排除这种情况吗?

答案 1 :(得分:3)

我走了很长的路,但终于把这个概念放在了我的脑海里,这就是我的工作:

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);
        });
    });

我的主要问题是使用ASIHTTPRequest startAsynchronous方法: http://allseeing-i.com/ASIHTTPRequest/How-to-use#using_blocks

这实际上是创造我的瓶颈,2000个图像得到,2000个异步调用试图制作。如果我已经在后台,startSynchronous方法工作得很好,一次只能尝试运行一次。

答案 2 :(得分:1)

据我了解,ASIHTTPRequest startSynchronous需要NSRunLoop,而GCD串行队列线程没有NSRunLoop。因此,它不起作用。

您的代码似乎根本不需要GCD串行队列。我认为GCD全局队列可以正常使用您的代码。例如,

- (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方法应该在主线程上执行,它需要主线程的NSRunLoop。