GCD和webView死锁

我发现了一个似乎在WebKit导致死锁的问题。 如果我从我的主线程运行这个代码,我正确地看到一个警报。 我可以点击警报上的“确定”button,它被解散,一切工作正常:

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

如果稍作修改,则警告消息仍会出现,但不能点击确定button – 您不能closures警报,并且如果进入应用程序,则会挂起在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]; }); 

有人能指出什么是错误的,导致挂起?

编辑:

相关问题:

使用dispatch_async或performSelectorOnMainThread在主线程上执行UI更改?
主队列上的performSelectorOnMainThread和dispatch_async有什么区别?
Grand Central Dispatch(GCD)与performSelector – 需要更好的解释

非常类似的问题:

使用GCD调用时,UIWebView stringByEvaluatingJavaScriptFromString在iOS5.0 / 5.1上挂起

这似乎只是UIWebView中的一个错误。 根据这个问题 ,它是在iOS 5中引入的,并且在iOS 4.3和更低版本中没有发生死锁。

有趣的是,在调用stringByEvaluatingJavaScriptFromString:之前提交一个UIAlertView stringByEvaluatingJavaScriptFromString:奇怪地防止了死锁:

 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引擎是在不同的线程上执行的,因此必须告诉主线程显示alert视图。 但是, stringByEvaluatingJavaScriptFromString:是一个返回值的阻塞调用。 只有通过单击确定解除警报后才能返回该值。 这是看似发生死锁的地方:从另一个线程中,我们告诉主线程告诉web视图运行JavaScript(这发生在另一个线程上),这反过来告诉主线程显示一个alert view一旦OK单击,将其返回值传回JavaScript。 只有当调用stringByEvaluatingJavaScriptFromString:返回时,我们传递给GCD的块才完成。

不过这肯定是个bug。 奇怪的是,当我首先显示一个UIAlertView时,死锁不会发生。 也许iOS在这种情况下将第二个警报视图放在某种types的队列中,这样可以防止死锁。 奇!

我认为这是在webview中陈述 图片 类参考

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

performSelector:withObject:afterDelay:inModes: 在默认情况下,它是NSDefaultRunLoopMode ,它本质上是primefaces的,而在dispatch_get_main_queue()非primefaces情况下,你必须改变线程的当前调度模式。

我希望它保持翔实。 此外,欢迎提供更多build议。

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

下一个您应该注意的事项是从UIAlertView -show和JavaScript alert(..)之间存在显着差异。 UIAlertView -showasynchronous操作。 您可以这样说,因为在解除警报之前,在调用之后代码执行会继续。 如果您在JavaScript中执行相同的实验,代码执行将停止,直到警报closures。

最后,调度队列和线程之间是有区别的。 你可以阅读更多关于它的线程 。 我怀疑JavaScript执行正在执行[NSThread isMainThread] ,它报告NO因为它在主队列,而不是主线程。 由于此检查报告为“ NO ,因此它对主队列执行dispatch_sync ,而主队列已经被阻塞,等待JavaScript队列。