dispatch_async 主队列上的块未在模态 运行 循环中执行

dispatch_async block on main queue is not executed in a modal run loop

我有以下代码检查外循环中的 RunLoop,然后使用 dispatch_after 分派到内循环中的 main_thread。我有两种情况会调用它,一次是在导航栏上按下按钮时,另一种情况是在 viewDidAppear 期间。当稍后调用代码时,它仍然停留在 RunLoop 中,我的断点永远不会到达我的 dispatch_after 块。为什么 dispatch_after 被屏蔽了?即使在代码正在执行时,我也需要这样做来更新一些进度指示器。很明显为什么这可能在一种情况下有效而在另一种情况下无效?我查看了堆栈,堆栈中没有其他内容。

// case 1:

// does not hit breakpoint
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.4 * NSEC_PER_SEC),     dispatch_get_main_queue(), ^
        {
            [ self longRunningOp ];
        });

// case 2:

// This works fine! 
[ self longRunningOp ];



-(void) longRunningOp
{
    bool dispatched = false;
    while (!finished)
    {
        if (dispatched)
        {
            // reusing the same date to avoid buffers building up!
            date = [ date initWithTimeIntervalSinceNow:0 ];
            [ [ NSRunLoop currentRunLoop ] runMode: NSDefaultRunLoopModes beforeDate:date ];
            continue;
        }

        dispatched = true;

        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.001 * NSEC_PER_SEC), dispatch_get_main_queue(), ^() {
            // In second case breakpoint never gets here!

            // OPENGL OPS HERE!

            dispatched = false;
        }

    } );
}

我也注意到了其他的不同。

成功案例: 在它工作的情况下,UIApplicationMain 会调用 CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION,当用户按下导航按钮时会发生这种情况。

失败案例: 而在失败的情况下,UIApplicationMain 调用 CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE,这发生在 viewDidAppear.

经过多次调试并认为我有一些 works.I 无法解释为什么会这样,但它为我解决了。我可以解释我为什么做出改变。我仔细检查了堆栈中的成功案例,发现如果堆栈顶部函数对应于 performSelector:withObject:afterDelay 调用而不是 dispatch_async 调用,它会起作用。这给了我一个线索,让我知道我能做什么。我用 performSelector:withObject:afterDelay 替换了最外面的 dispatch_asynch。它现在可以正常工作。我在下面列出了失败案例和成功案例。

 // This causes the queue to block; -- FAILURE CASE
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.4 * NSEC_PER_SEC),         dispatch_get_main_queue(), ^
    {
        [ self longRunningOp ];
    });


// This keeps the queue running;!!!!! - SUCCESS CASE
[ self performSelector:@selector(longRunningOp) withObject:nil afterDelay:0.4 ];

这是我天真的尝试来解释为什么它有效。

在我的 RunMode 调用中,我使用的是当前循环,因此此调用取决于当前循环的设置方式。显然,当前循环在顶层的设置有所不同,这取决于是否使用 performSelector:withObject:afterDelay 而不是使用 dispatch_async。进一步看来,单击导航栏会导致 performSelector:withObject:afterDelay 调用,与让顶层成为分派异步相比,进入顶层堆栈是更理想的状态。
我怀疑这是最好的解释。但这是我目前最好的。

在你的失败案例中,你在堆栈上看到 CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE,运行 循环正在“服务”主调度 queue,这意味着它是 运行在主 queue 上 queued 的所有块。其中一个块最终发送 viewDidAppear 消息。所以堆栈看起来像这样:

-[ViewController viewDidAppear:]
-[UIViewController _setViewAppearState:isAnimating:]
-[UIViewController _endAppearanceTransition:]
-[UINavigationController navigationTransitionView:didEndTransition:fromView:toView:]
__49-[UINavigationController _startCustomTransition:]_block_invoke
-[_UIViewControllerTransitionContext completeTransition:]
__53-[_UINavigationParallaxTransition animateTransition:]_block_invoke95
-[UIViewAnimationBlockDelegate _didEndBlockAnimation:finished:context:]
-[UIViewAnimationState sendDelegateAnimationDidStop:finished:]
-[UIViewAnimationState animationDidStop:finished:]
CA::Layer::run_animation_callbacks(void*)
_dispatch_client_callout
_dispatch_main_queue_callback_4CF
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
__CFRunLoopRun
CFRunLoopRunSpecific
GSEventRunModal
UIApplicationMain
main
start

现在假设在 viewDidAppear: 中,你将另一个块放在主调度 queue 上,然后你 运行 递归地进入主 运行 循环,所以堆栈看起来像这样:

__CFRunLoopRun
CFRunLoopRunSpecific
-[NSRunLoop(NSRunLoop) runMode:beforeDate:]
-[ViewController viewDidAppear:]
-[UIViewController _setViewAppearState:isAnimating:]
-[UIViewController _endAppearanceTransition:]
-[UINavigationController navigationTransitionView:didEndTransition:fromView:toView:]
__49-[UINavigationController _startCustomTransition:]_block_invoke
-[_UIViewControllerTransitionContext completeTransition:]
__53-[_UINavigationParallaxTransition animateTransition:]_block_invoke95
-[UIViewAnimationBlockDelegate _didEndBlockAnimation:finished:context:]
-[UIViewAnimationState sendDelegateAnimationDidStop:finished:]
-[UIViewAnimationState animationDidStop:finished:]
CA::Layer::run_animation_callbacks(void*)
_dispatch_client_callout
_dispatch_main_queue_callback_4CF
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
__CFRunLoopRun
CFRunLoopRunSpecific
GSEventRunModal
UIApplicationMain
main
start

现在您似乎认为 运行 循环应该在此递归调用中再次为主调度 queue 服务。但它不能那样做。主要调度 queue 是一个 serial queue,这意味着它一次不会 运行 超过一个块。已经有一个主要的 queue 块 运行ning,称为 CA::Layer::run_animation_callbacks(void*)。在该块 returns 之前,没有其他主要 queue 块可以启动。 运行 循环知道这一点,因此它不会尝试在这个递归调用中为主要 queue 提供服务。

由于您在问题中发布的代码实际上没有任何作用,因此很难为您提供任何具体帮助。但是,您提到了 OpenGL。 Apple OpenGL ES Programming Guide for iOS 有一章标题为“并发和 OpenGL ES”,讨论如何将处理移动到后台线程或 queue。