主队列上的dispatch_async块不在模式运行循环中执行

时间:2016-03-19 21:08:34

标签: ios deadlock nsrunloop dispatch-async

我有以下代码检查外环中的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上。

2 个答案:

答案 0 :(得分:2)

在失败的情况下,您在堆栈上看到CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE,运行循环正在“服务”主调度队列,这意味着它正在运行已在主队列上排队的所有块。其中一个块最终发送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:中,你在主调度队列上放了另一个块,然后递归地运行主运行循环,所以堆栈看起来像这样:

__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

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

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

答案 1 :(得分:0)

经过多次调试并认为我有一些有用的东西。我无法解释它为什么会这样,但它为我解决了它。我可以解释为什么我做了改变。我仔细检查了堆栈的成功案例,并发现如果堆栈函数的顶部对应于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调用,这是一个更理想的状态,必须进入顶级堆栈,而不是让顶层是调度异步。
我怀疑它是最好的解释。但它是我目前最好的。