6

我发现了一个似乎导致 WebKit 死锁的问题。如果我从我的主线程运行此代码,我会正确地看到一个警报。我可以点击警报上的“确定”按钮,它会消失并且一切正常:

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

如果我稍作修改,仍然会出现警报消息,但无法点击 OK 按钮 - 您无法关闭警报,如果您闯入应用程序,它会挂在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 上挂起

4

3 回答 3

5

我认为它在webview 图片类参考中有所说明

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

performSelector:withObject:afterDelay:inModes:. 默认情况下,使用 Respective 模式,它NSDefaultRunLoopMode本质上是原子的,而在您的非原子情况下,dispatch_get_main_queue()您必须更改当前的线程调度模式。

我希望它仍然提供信息。此外,欢迎提出更多建议。

于 2013-10-31T10:29:02.950 回答
4

这似乎只是 UIWebView 中的一个错误。根据这个问题,它是在iOS 5 中引入的,在iOS 4.3 及以下版本没有死锁。

有趣的是,在调用之前显示一个 UIAlertViewstringByEvaluatingJavaScriptFromString:奇怪地防止了死锁:

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(这发生在另一个线程上),这反过来又告诉主线程显示一个警报视图,它只能单击 OK 后将其返回值返回给 JavaScript。只有当调用stringByEvaluatingJavaScriptFromString:返回时,我们传递给 GCD 的块才完成。

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

于 2013-10-30T15:37:13.183 回答
0

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

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

最后,调度队列和线程之间存在差异。你可以在这个线程中阅读更多关于它的信息。我怀疑 JavaScript 执行正在执行[NSThread isMainThread]并且它正在报告NO,因为它在主队列上,而不是主线程上。由于这个检查是报告NO的,它对dispatch_sync已经阻塞等待 JavaScript 队列的主队列进行检查。

于 2013-11-03T15:43:45.600 回答