我有一个Swing
应用,并进行JNI
方法调用以打开NSOpenPanel
。在某些计算机上(不幸的是我没有找到它们之间的相似之处)它完全挂起了应用程序。在大多数计算机上它可以正常工如果代码在特定的Mac上挂起应用程序,则每次执行时都会执行此操作。
以下是我打开NSOpenPanel的方法:
JNF_COCOA_ENTER(env);
// My helper Obj-c object to make a selector call
OpenFileObject *openFile = [[OpenFileObject alloc] init];
if ([NSThread isMainThread])
[openFile showOpenFileDialog];
else
[JNFRunLoop performOnMainThread:@selector(showOpenFileDialog) on:openFile withObject:nullptr waitUntilDone:TRUE];
// ...Handles results
JNF_COCOA_EXIT(env);
这里是showOpenFileDialog
方法:
NSOpenPanel *panel = [NSOpenPanel openPanel];
[panel setCanChooseFiles:canChooseFiles];
[panel setCanChooseDirectories:canChooseFolders];
[panel setAllowsMultipleSelection:allowMultiSelection];
[panel setAllowedFileTypes:fileTypes];
[panel setTitle:dialogTitle];
if ([panel runModal] == NSFileHandlingPanelOKButton)
urls = [[panel URLs] copy];
else
urls = nullptr;
这是一个挂起报告:https://gist.github.com/4207956
有什么想法吗?
答案 0 :(得分:1)
首先,我怀疑这与您的问题有关,但我会谨慎使用nullptr
和TRUE
作为performOnMainThread:...
的参数而不是我会这样做:
[JNFRunLoop performOnMainThread:@selector(showOpenFileDialog) on:openFile withObject:nil waitUntilDone:YES];
为了安全起见。
从堆栈跟踪中,假设它是一个大堆栈,并且您只是将它分开以添加有关辅助对象方法调用的注释,看起来正在采用的代码路径是[NSThread isMainThread]
返回NO的路径。这必须意味着它在后台线程上(__NSThreadPerformPerform
然后正在进行JNFRunLoop
的出价。
堆栈永远不会离开NSOpenPanel
的初始化路径,不知何故,在底层深处,它再次访问运行循环的东西。在我看来,似乎发生了某种僵局。
如果JNFRunLoop
正在另一个线程中等待执行showOpenFileDialog
方法以在主运行循环上完成,而openPanel
中的某些东西正试图等待同样的东西运行循环,可能会导致死锁。
我不熟悉Java和Cocoa之间的集成,但是可能有某种方法可以避免在非主线程上执行第一段代码吗?
或者,您可以尝试使用:
[openFile performSelectorOnMainThread:@selector(showOpenFileDialog) withObject:nil waitUntilDone:YES];
在非主线程路径中?
答案 1 :(得分:0)
我找到了罪魁祸首。问题是由于另一个应用程序使用了MacOS的Accessibility API。显示模式对话框时,主Java框架不会响应所需的本机Accessibility API调用,这会挂起整个应用程序。避免使用本机模态对话框即可解决问题。
例如,对于NSOpenPanel,我们应将[panel runModal]
更改为[panel beginSheetModalForWindow]
。这样就解决了问题。
答案 2 :(得分:0)
对此问题可能有更好的解决方案。
死锁是由两个相互等待的线程引起的:AppKit主线程和AWT事件线程。您的调用本机方法的Java代码正在AWT事件线程上运行。本机方法在等待创建要在AppKit主线程上执行的NSOpenPanel的代码时阻止AWT事件线程。同时,可以在AppKit主线程上处理其他事件,如果这些事件涉及AWT窗口,则可以对Java进行上调。由于Java倾向于使用AWT在其UI上进行操作,因此上行调用可能会将事件发布到AWT事件队列上,并阻止等待事件被处理。但是,只要在您的本机方法中阻止了AWT事件线程,就永远不会处理该事件。因此,陷入僵局。
解决方案是运行您的Java代码,该Java代码从新的Java线程调用本地方法,以便阻止此后台线程而不是AWT事件线程。如果您的代码必须在需要此计算结果的AWT事件线程上运行,请让它创建一个java.awt.SecondaryLoop并运行该循环,同时等待调用您的本地方法的线程接收结果。 SecondaryLoop将处理上行调用所发布的事件,从而允许AppKit主线程继续工作,并最终创建您的NSOpenPanel。 SecondaryLoop的Javadoc给出了如何执行此操作的示例。