11

我在后台线程上有一些计算工作,之后我需要更新一些 calayer 的变换,我尝试使用

dispatch_async(dispatch_get_main_queue(), ^{calayer.transform = newTransform});

CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, ^(void) {calayer.transform = newTransform});

我只是认为它们是相同的,但我发现使用 dispatch_async 时 calayer 工作得非常顺利(也许?)。这两个功能有什么不同?

4

4 回答 4

19

这里的主要区别是CFRunLoopPerformBlock允许您指定执行块的特定运行循环模式,而dispatch_async(dispatch_get_main_queue(),...)仅在普通模式下执行。也许更适合您看到的性能问题,不会CFRunLoopPerformBlock唤醒主线程。从文档中:CFRunLoopPerformBlock

此方法仅将块入队,不会自动唤醒指定的运行循环。因此,块的执行发生在下次运行循环唤醒以处理另一个输入源时。如果您希望立即执行工作,则必须使用 CFRunLoopWakeUp 函数显式唤醒该线程。

在实践中,这通常意味着在运行循环唤醒之前(即用户事件发生、定时器触发、运行循环源触发、接收到 mach 消息等),您的块不会被执行。按照设计,GCD 不是,基于运行循环的 API;主队列和主线程运行循环之间的关系实际上是一个实现细节。如果需要服务主队列,我希望该实现能够唤醒运行循环本身。

相反,没有信息,我强烈怀疑这是性能差异的根源。CFRunLoopWakeUp如果您在调用 后立即添加调用,我希望性能相似CFRunLoopPerformBlock

于 2013-02-10T19:07:13.077 回答
7

GCD 的主队列是一个串行队列。因此,它一次只能运行一个任务。即使该任务运行内部运行循环(例如,运行模式对话框),提交到主队列的其他任务在完成之前也无法运行。

CFRunLoopPerformBlock()只要运行循环在其中一种目标模式下运行,使用提交的任务就可以运行。这包括运行循环是否从使用CFRunLoopPerformBlock().

考虑以下示例:

CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, ^{
    printf("outer task milestone 1\n");
    CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, ^{
        printf("inner task\n");
    });
    [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
    printf("outer task milestone 2\n");
});

产生如下输出:

outer task milestone 1
inner task
outer task milestone 2

虽然这样:

dispatch_async(dispatch_get_main_queue(), ^{
    printf("outer task milestone 1\n");
    dispatch_async(dispatch_get_main_queue(), ^{
        printf("inner task\n");
    });
    [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
    printf("outer task milestone 2\n");
});

产生:

outer task milestone 1
outer task milestone 2
inner task
于 2014-10-02T22:49:57.320 回答
2

我有时会一起使用它们:

dispatch_async(dispatch_get_main_queue(), ^(void) {
    CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopDefaultMode, ^{
        // stuff
    });
});

我使用它向主线程发送一个块,当 UIScrollview 滚动时,该块将执行而不会导致“故障”。

我最近也在使用:

CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopDefaultMode, ^{
    // stuff
});

替代:

[self performSelector:@selector(myMethod:) withObject:myObject afterDelay:0]

为了将代码的执行推迟到下一次通过runloop。这样我就不必创建一个特殊的方法来包含我想要执行的代码,也不必将所有要执行的参数包装到单个 (id) myObject 中。

于 2014-05-23T00:36:36.573 回答
0

一个可能导致严重麻烦的微妙之处是由于 CF 块“发送”到的深处的一个错误CFRunLoopPerformBlock并不总是按照它们发送的顺序被调用。如果顺序对您的程序很重要,那么您需要自己找到一种方法来提供它。由于这个原因以及另一个答案中提到的嵌套,文档CFRunLoopPerformBlock不应该使用队列术语。可悲的是,这个错误也悄悄地传播到了一些方法中NSRunLoop(因为,当然,他们调用CFRunLoopPerformBlock)。

于 2020-10-30T17:56:14.117 回答