iPhone - Grand Central Dispatch 主线程

146

我一直在我的应用程序中成功地使用GCD,但我想知道使用这种东西的真正优势是什么:

dispatch_async(dispatch_get_main_queue(), ^{ ... do stuff

甚至更多
dispatch_sync(dispatch_get_main_queue(), ^{ ... do stuff

我的意思是,在这两种情况下,您都在将一个块发送到主线程上执行,也就是应用程序运行的地方,这并不会帮助减轻负载。在第一种情况下,您无法控制块将在何时运行。我见过有些块会在您发出它们的半秒钟后才执行。第二种情况类似于...
[self doStuff];

好的?

我想知道你们怎么想。


9
顺便提一下,将主队列(main queue)放入dispatch_sync中会导致死锁。 - Brooks Hanes
5
刚在文档中读到:“与dispatch_async不同,[dispatch_sync]在块完成之前不返回。调用此函数并针对当前队列会导致死锁。”...但也许我理解错了...(当前队列并不意味着主线程)。如果我理解有误,请纠正。 - Brooks Hanes
4
@BrooksHanes并不总是正确的。 如果您已经在主线程上,则会导致死锁,但如果不是,则不会发生死锁。 请参见此处:https://dev59.com/aGsz5IYBdhLWcg3wj4kc#37424446 - mfaani
6个回答

296

通常情况下,将一个块派发到主队列是从后台队列中完成的,以表示某些后台处理已经完成,比如:

- (void)doCalculation
{
    //you can use any string instead "com.mycompany.myqueue"
    dispatch_queue_t backgroundQueue = dispatch_queue_create("com.mycompany.myqueue", 0);

    dispatch_async(backgroundQueue, ^{
        int result = <some really long calculation that takes seconds to complete>;

        dispatch_async(dispatch_get_main_queue(), ^{
            [self updateMyUIWithResult:result];
        });    
    });
}

在这种情况下,我们在后台队列上进行了一个漫长的计算,并且需要在计算完成时更新我们的UI界面。通常需要从主队列更新UI界面,因此我们使用第二个嵌套的dispatch_async向主队列发出“信号”。

可能还有其他情况需要返回主队列,但通常是通过在调度到后台队列的块中嵌套执行的方式来实现的。

  • 后台处理完成->更新UI
  • 在后台队列上处理数据块->信号主队列开始下一个数据块
  • 在后台队列上收到网络数据->向主队列发出消息已到达的信号
  • 等等

至于为什么您可能希望从主队列调度到主队列...好吧,通常不会这样做,尽管您可以想象这样做是为了安排一些工作,在下一次运行循环周围进行操作。


啊,我懂了。所以,我的想法是正确的。如果你已经在主队列上,那么这样做没有任何优势,只有当你在其他队列上并希望更新UI时才有用。谢谢。 - Duck
10
那不是程序错误,那是预期行为。不过这个行为并不是很有用,但在使用 dispatch_sync 时,你总是需要注意死锁问题。你不能指望系统始终保护你免受程序员错误的影响。 - Robin Summerhill
2
这里的backgroundQueue是什么?我该如何创建backgroundQueue对象? - Nilesh Tupe
@NileshTupe请检查更新的答案,请注意Robin已经以很好的方式描述了它!+1。 - swiftBoy
我不太理解这里的问题。所以第一次调用后台队列启动了这个漫长的计算。但由于它是异步调用的,这是否意味着嵌套调用到主队列的分派可能在计算完成之前开始? - MikeG
显示剩余3条评论

17

将块分派到主队列并在主线程中执行 可能 是有用的。这可以使主队列有机会处理已排队的其他块,从而不会仅仅阻塞其他所有内容的执行。

例如,您可以编写一个基本上是单线程服务器,但仍然处理许多并发连接。只要队列中没有任何单个块花费过长时间,服务器就能对新请求做出响应。

如果您的程序除了一直响应事件外什么都不做,那么这可能是非常自然的。您只需设置事件处理程序以在主队列上运行,然后调用dispatch_main(),您可能根本不需要担心线程安全问题。


11

希望我理解你的问题是询问dispatch_async和dispatch_sync之间的区别?

dispatch_async

会将代码块异步地分派到队列中。也就是说,它会将代码块发送到队列中,并在继续执行方法中剩余的代码之前不等待其返回。

dispatch_sync

将块同步分派到队列中。这将防止方法中剩余的代码执行,直到块完成执行。

我通常使用dispatch_async分派到后台队列,以便使工作脱离主队列并利用设备可能具有的任何额外核心。如果需要更新UI,则会dispatch_async回到主线程。

祝好运


1
谢谢,但我在询问将某些内容发送到主队列、处于主队列的优势。 - Duck

9

其中一个有用的场景是 UI 操作,例如在长时间操作之前设置 spinner:

- (void) handleDoSomethingButton{

    [mySpinner startAnimating];

    (do something lengthy)
    [mySpinner stopAnimating];
}

这样做不起作用,因为您在执行冗长的任务时阻塞了主线程,并且没有让UIKit启动旋转器。

- (void) handleDoSomethingButton{
     [mySpinner startAnimating];

     dispatch_async (dispatch_get_main_queue(), ^{
          (do something lengthy)
          [mySpinner stopAnimating];
    });
}

将控制权返回给运行循环,该循环将安排UI更新,启动旋转器,然后获取调度队列中的下一个事项,即您的实际处理。当处理完成时,将调用停止动画,并返回到运行循环,在那里使用停止更新UI。


@Jerceratops 是的,但它允许当前运行循环完成。 - Dan Rosenstark
3
是的,但这仍然很糟糕。它仍然会阻塞用户界面。我可能会在按下这个按钮后立即按下另一个按钮,或者尝试滚动屏幕。"(做一些耗时的事情)" 不应该在主线程上执行,而使用 dispatch_async 让按钮点击 "完成" 也不是可接受的解决方案。 - Jerceratops

8

Swift 3、4和5

在主线程上运行代码

DispatchQueue.main.async {
    // Your code here
}

0

异步意味着异步执行,大多数情况下应该使用它。您永远不应该在主线程上调用同步操作,因为这会锁定您的用户界面直到任务完成。以下是在Swift中更好的实现方法:

runThisInMainThread { () -> Void in
    // Run your code like this:
    self.doStuff()
}

func runThisInMainThread(block: dispatch_block_t) {
    dispatch_async(dispatch_get_main_queue(), block)
}

它已经作为我的仓库的标准函数包含在内了,来看看吧:https://github.com/goktugyil/EZSwiftExtensions


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