NSOpenPanel挂起java应用程序

时间:2012-12-04 19:50:22

标签: java macos cocoa java-native-interface

我有一个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

有什么想法吗?

3 个答案:

答案 0 :(得分:1)

首先,我怀疑这与您的问题有关,但我会谨慎使用nullptrTRUE作为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给出了如何执行此操作的示例。