如何防止dispatch_group卡住?

时间:2014-05-14 16:21:03

标签: objective-c multithreading grand-central-dispatch

如何阻止dispatch_group卡住?我发现如果没有加载我尝试加载的图像之一(例如由于坏URL),可能会卡在以下代码中(有或没有dispatch_group_wait调用)。 dispatch_group_notify中的块永远不会被调用。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_group_t group = dispatch_group_create();

for (...) {
        if (...) {
            dispatch_group_enter(group);
            dispatch_async(queue, ^{

               [self loadImageWithUrl:url onCompletion:^{
                     dispatch_group_leave(group);
               }];
            });
        }
    }

    dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)));
    dispatch_group_notify(group, queue, ^{
        NSLog(@"load image complete");
    });

4 个答案:

答案 0 :(得分:8)

当组完成时,

dispatch_group_notify将其块排队。你的小组永远不会完成所以不要使用dispatch_group_notify。只需使用dispatch_group_wait等待超时,然后调度您的块:

    ...    
    dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)));
    dispatch_async(queue, ^{
        NSLog(@"load image complete");
    });

如果你想用超时来模仿dispatch_group_notify,只需在它自己的异步块中执行上述操作:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)));
  dispatch_sync(queue, ^{
      NSLog(@"load image complete");
  });
});

请注意,您可以使用dispatch_group_wait的返回值来确定是否所有内容都已完成,或者是否超时(如果这对您有用)。

请记住,之前的块不会被取消,因此它们最终可能会运行完成块。如果这是一个问题,您可能需要向系统添加取消逻辑。

答案 1 :(得分:4)

我认为问题不在于您的群组通知流程。对我来说,跳出来的问题是,不是试图处理未调用完成块的情况,而是更改loadImageWithUrl以确保总是调用完成块,无论成功与否。您甚至可能希望向块中添加NSError参数或类似内容,因此如果出现故障(例如警告用户或启动等待连接的可达性进程),将通知调用方。在尝试重试之前重新建立等等。

所以,它可能看起来像:

- (void)loadImageWithUrl:(NSURL *)url loadImageWithUrl:(void (^)(NSError *error))block
{
    BOOL success;
    NSError *error;

    // do your download, setting `success` and `error` appropriately

    // then, when done, call the completion block, whether successful or not

    if (block) {
        if (success) {
            block(nil);
        } else {
            block(error);
        }
    }
}

显然,上述细节完全取决于你如何做这些请求,但这是基本的想法。然后,您只需确保调用者已更改为包含此额外参数:

for (...) {
    if (...) {
        dispatch_group_enter(group);
        dispatch_async(queue, ^{

            [self loadImageWithUrl:url onCompletion:^(NSError *error){
                if (error) {
                    // handle the error however you want, if you want
                }

                dispatch_group_leave(group);
            }];
        });
    }
}

我不太关心你如何选择处理错误而不是鼓励你确保调用完成块而不管下载是否成功。这可以确保您进入群组的次数与您离开群组的次数完全平衡。


话虽如此,在下载许多资源时,GCD不适合这项任务。问题在于,将GCD限制为可以同时执行多少并发任务并非易事。通常,您希望限制可以并发运行的请求数。这样做是因为(a)对于有多少NSURLSessionTaskNSURLConnection请求可以同时运行存在限制; (b)如果运行速度超过此值,则在连接速度较慢时,您会面临不必要的超时请求; (c)您可以减少应用的峰值内存使用量;但是(d)你仍然喜欢并发,在内存使用和最佳网络带宽优化之间取得平衡。

要实现这一点,常见的解决方案是使用操作队列而不是GCD的调度队列。然后,您可以将下载请求包装在NSOperation个对象中,并将这些网络操作添加到NSOperationQueue,并为其设置了合理的maxConcurrentOperationCount(例如4或5)。而不是调度组通知,您可以添加完成操作,该操作取决于您添加到队列中的其他操作。

如果您不想自己实现,可以使用AFNetworkingSDWebImage,这有助于使用操作队列下载图像来管理下载过程。


最后一个想法是许多应用程序采用延迟加载过程,其中图像在需要时无缝加载。它避免了消耗太多的用户数据计划执行一些批量下载(或者冒着用户首先需要的图像在他们不需要的一堆其他图像后面积压的风险)。 AFNetworking和SDWebImage都提供UIImageView类别,提供极其简单的延迟加载图像。

答案 2 :(得分:0)

是否可以在内部块中同步加载图像?这样您就可以使用dispatch_group_async()而不是手动跟踪进入/离开范例。

我怀疑错误在于块是如何完成的以及上下文如何不正确的,我从你离开组的块/上下文之外输入一个组似乎很奇怪。

最后,你确定总是调用图像加载的完成块吗?是否有可能在请求失败时未调用完成,因此组计数器永远不会递减?

抱歉我的初步答案顺便说一下,我完全误解了这个问题。

编辑:现在我想到了目标是什么(在所有图像加载后同步),似乎这种方法并不合理。代码是否需要阻塞才能加载所有图像?如果没有,那么假设所有完成块都在单个线程上触发,我只需跟踪已触发的块数并减少完成块中的计数。当最后一个完成时,可以执行当前dispatch_group_notify()的内容。

另一个,或许更具有未来性的选择是重构图像加载代码,以提供获取图像的同步方式(意味着在这种情况下使用)或提供能够进行调度的异步API group / queue,这显然假设图像加载器的内部使用GCD。

最后,您可以编写一个NSOperation子类,它负责单个图像加载过程,然后这些操作可以在NSOperationQueue中使用(从GCD提供更多的抽象),可以很容易地用来跟踪多少正在进行中并且一切都结束了。

答案 3 :(得分:0)

问题在于您使用dispatch_group_async()。除非您正在执行要以异步方式完成的同步任务,否则不应使用它。您的loadImageWithUrl()已经异步。这就是你应该如何构建dispatch_group的使用。

dispatch_group_t group = dispatch_group_create();

for (...) {
    if (...) {
        dispatch_group_enter(group);

        [self loadImageWithUrl:url onCompletion:^{
            dispatch_group_leave(group);
        }];
    }
}

dispatch_group_notify(group, queue, ^{
    NSLog(@"load image complete");
});

同样dispatch_group_wait是使用dispatch_group_notify的替代方法。只有在您想要同步等待组完成时才应该使用它。