与GCD和webView的死锁

时间:2013-10-23 02:28:23

标签: ios objective-c webkit grand-central-dispatch

我发现了一个似乎导致WebKit陷入僵局的问题。如果我从我的主线程运行此代码,我正确地看到一个警报。我可以点击" OK"警报上的按钮,它解散,一切运作良好:

[theWebView stringByEvaluatingJavaScriptFromString:@"alert('hi');"];

如果我做了一些修改,那么警报信息仍会显示,但是无法点按“确定”按钮 - 您无法关闭警报,如果您闯入应用程序,则会挂起stringByEvaluatingJavaScriptFromString电话:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    dispatch_async(dispatch_get_main_queue(), ^{
        [theWebView stringByEvaluatingJavaScriptFromString:@"alert('hi');"];
    });
});

这两者中的不同之处在于,在第二个中,它在调度队列的上下文中在主线程中运行JS。

另一方面,如果我执行以下操作,则不会发生挂起:

- (void) showHi:(id) it
{
    [(UIWebView*)it stringByEvaluatingJavaScriptFromString:@"alert('hi');"];
}

....

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    [self performSelectorOnMainThread:@selector(showHi:) withObject:theWebView waitUntilDone:NO];
});

有人可以对导致悬挂的问题有所了解吗?

编辑:

相关问题:

Perform UI Changes on main thread using dispatch_async or performSelectorOnMainThread?
Whats the difference between performSelectorOnMainThread and dispatch_async on main queue?
Grand Central Dispatch (GCD) vs. performSelector - need a better explanation

非常相似的问题:

UIWebView stringByEvaluatingJavaScriptFromString hangs on iOS5.0/5.1 when called using GCD

3 个答案:

答案 0 :(得分:5)

我认为已在webview Image类引用

中说明了这一点

现在,对于死锁的情况,您可以通过替换它来模拟死锁 [self performSelectorOnMainThread:@selector(showHi:) withObject:theWebView waitUntilDone:NO];

performSelector:withObject:afterDelay:inModes:。 默认情况下,使用“分别”模式为NSDefaultRunLoopMode,这本质上是原子的,而在dispatch_get_main_queue()的非原子情况下,您必须更改当前的线程分派模式。

我希望它仍然有用。此外,欢迎提出更多建议。

答案 1 :(得分:4)

这似乎只是UIWebView中的一个错误。根据{{​​3}},它是在iOS 5中引入的,并且在iOS 4.3及更低版本上执行死锁。

有趣的是,在调用stringByEvaluatingJavaScriptFromString:之前提出UIAlertView会奇怪地防止死锁:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    dispatch_async(dispatch_get_main_queue(), ^{
        UIAlertView *message = [[UIAlertView alloc] initWithTitle:@"Test"
                                                          message:@"Test"
                                                         delegate:nil
                                                cancelButtonTitle:@"OK"
                                                otherButtonTitles:nil];
        [message show];

        [theWebView stringByEvaluatingJavaScriptFromString:@"alert('hi');"];
    });
});

但是,我的理论是这样的:当我在死锁发生后暂停执行时,我看到WebThread在__psynch_mutexwait停止。由于JavaScript引擎在不同的线程上执行,因此它必须告诉主线程显示警报视图。但是,stringByEvaluatingJavaScriptFromString:是一个返回值的阻塞调用。只有在单击“确定”关闭警报后才能返回该值。这就是死锁似乎发生的地方:从另一个线程我们告诉主线程告诉web视图运行JavaScript(发生在另一个线程上),这反过来告诉主线程显示一个警报视图,该视图只能单击“确定”后,将其返回值返回到JavaScript。只有当stringByEvaluatingJavaScriptFromString:的调用返回时,我们传递给GCD的块才会完成。

但是,它必定是一个bug。奇怪的是,当我首先显示UIAlertView时,不会发生死锁。在这种情况下,iOS可能会在某种队列上放置第二个警报视图,从而防止死锁。奇!

答案 2 :(得分:0)

有几件事可能是其中的一个因素。正如其他人所提到的,JavaScript不在主线程上执行,而是在自己的后台线程上执行。您可能已经注意到代码的执行会一直等到JavaScript完成。我不知道代码中的具体实现,但听起来它在内部使用dispatch_syncdispatch_barrier_sync

您应该注意的下一个项目是UIAlertView -show和JavaScript alert(..)提供的警报之间存在显着差异。 UIAlertView -show以异步方式运行。您可以这样说,因为在警报解除之前调用-show后代码执行仍在继续。如果您在JavaScript中执行相同的实验,代码执行将停止,直到警报关闭。

最后,调度队列和线程之间存在差异。您可以在this thread中了解更多信息。我怀疑JavaScript执行正在执行[NSThread isMainThread]并报告NO因为它在主队列中,而不是主线程。由于此检查报告NO,因此它会对主队列执行dispatch_sync,主队列已被阻止,等待JavaScript队列。