理解 dispatch_async

246

我对这段代码有疑问

dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSData* data = [NSData dataWithContentsOfURL: 
      kLatestKivaLoansURL];
    [self performSelectorOnMainThread:@selector(fetchedData:) 
      withObject:data waitUntilDone:YES];
});

这段代码的第一个参数是

dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) 

我们是否要求此代码在全局队列上执行串行任务,而全局队列的定义本身就是返回给定优先级级别的全局并发队列?

使用dispatch_get_global_queue相比主队列有什么优势?

我感到困惑。请帮忙更好地理解这个问题。


1
你最好将代码分成几行,这样更有意义。将dispatch_get_global_queue保存在一个变量类型为dispatch_queue_t myQueue中。只传递myQueue到你的dispatch_async中会更易读。 - Alex Cio
3个回答

540

使用默认队列而不是主队列的主要原因是在后台运行任务。

例如,如果我正在从互联网下载文件并且想要更新用户下载进度,我将在优先级为默认队列中运行下载,并异步地在主队列中更新UI。

dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
    //Background Thread
    dispatch_async(dispatch_get_main_queue(), ^(void){
        //Run UI Updates
    });
});

我正在按照您建议的方式进行,但是当我在运行UI更新时调用[self.tableView reloadData]时,uiTableViewCell不会立即更新。它需要大约4或5秒钟的时间。这让我疯狂了好几天了。 - GrandSteph
@GrandSteph 我对那个方法不太熟悉。也许那个方法只需要5秒钟就能运行完。使用dispatch_async的重要之处在于它允许你在后台执行任务而不会阻塞主线程。 - Liftoff
@GrandSteph,通常这意味着您的UITableView DataSource或Delegate方法太长了,因为一旦您调用“reloadData”,DataSource和一些Delegate函数将在视图更新之前被调用。有时,如果这些函数中有一些重量级计算,它会花费很长时间。 - antonio081014
2
0 代表什么意思? - mfaani
3
@Honey 中的0是“flags”参数,目前没有任何作用。根据文档:“这些标志是保留给将来使用的。对于此参数始终指定为0。” - Liftoff
显示剩余2条评论

200
所有的DISPATCH_QUEUE_PRIORITY_X队列都是并发队列(意味着它们可以同时执行多个任务),并且是FIFO的,这意味着给定队列内的任务将按照“先进先出”的顺序开始执行。与此相比,主队列(从dispatch_get_main_queue()获取)是一个串行队列(任务将按照接收顺序开始执行和完成执行)。
因此,如果您向DISPATCH_QUEUE_PRIORITY_DEFAULT发送1000个dispatch_async()块,则这些任务将按照您将它们发送到队列中的顺序开始执行。同样适用于HIGH、LOW和BACKGROUND队列。您发送到这些队列中的任何内容都在后台上的不同线程上执行,远离主应用程序线程。因此,这些队列适合执行后台下载、压缩、计算等任务。
请注意,每个队列的执行顺序都是以FIFO为基础的。因此,如果您将1000个dispatch_async()任务均匀分割并按顺序发送到四个不同的并发队列BACKGROUND、LOW、DEFAULT和HIGH中(即您在HIGH队列上调度最后的250个任务),那么您看到开始执行的第一个任务很可能是在HIGH队列上,因为系统已经理解了您需要尽快将这些任务提交到CPU中。
请注意,我说“将按顺序开始执行”,但请记住,作为并发队列,根据每个任务所需的时间长度,它们不一定会以相同的顺序完成执行。
根据苹果公司的说法:

https://developer.apple.com/library/content/documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationQueues/OperationQueues.html

一个并发调度队列在你有多个可以并行运行的任务时非常有用。并发队列仍然是一个队列,因为它按照先进先出的顺序出队任务;但是,并发队列可能在任何之前的任务完成之前出队其他任务。由并发队列执行的任务数量在任何给定时刻是可变的,并且随着应用程序中的条件变化而动态改变。许多因素会影响并发队列执行的任务数量,包括可用核心数、其他进程正在执行的工作量以及其他串行调度队列中任务的数量和优先级。
基本上,如果您将这1000个dispatch_async()块发送到默认、高、低或后台队列,它们将按照您发送它们的顺序开始执行。然而,较短的任务可能会在较长的任务之前完成。造成这种情况的原因是如果有可用的CPU核心或者当前队列的任务正在执行计算非密集型的工作(从而使系统认为它可以并行分派其他任务,而不考虑核心数)。
并发级别完全由系统处理,并基于系统负载和其他内部确定的因素。这就是Grand Central Dispatch(dispatch_async()系统)的美妙之处 - 你只需将工作单元设置为代码块,为它们设置一个优先级(基于你选择的队列),然后让系统处理其余部分。
所以回答你上面的问题:你部分正确。你正在“要求该代码”在指定的优先级全局并发队列上执行并发任务。块中的代码将在后台执行,任何其他(类似的)代码都可能会并行执行,具体取决于系统对可用资源的评估。
另一方面,“主”队列(来自dispatch_get_main_queue())是一个串行队列(不是并发队列)。发送到主队列的任务将始终按顺序执行,并且将始终按顺序完成。这些任务也将在UI线程上执行,因此适合使用进度消息、完成通知等更新您的UI。

+1,但我认为在实践中,并发队列是FIFO还是随机顺序并不重要。如果您在循环中启动5个任务,则假定它们基本上同时启动。即使它们执行相同的代码,也不能保证第一个任务的第一个I/O操作会在第五个任务之前发生。另一方面,对于串行队列,FIFO行为是必不可少的,而且在我看来,这是两种队列类型之间的定义性差异。 - Gerhard Wesp
令人难以置信的解释。鼓掌! - Okhan Okbay

38

Swift版本

这是David Objective-C答案的Swift版本。您可以使用全局队列在后台运行任务,并使用主队列更新用户界面。

DispatchQueue.global(qos: .background).async {
    
    // Background Thread
    
    DispatchQueue.main.async {
        // Run UI Updates
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接