1

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

有任何想法吗?

4

3 回答 3

1

首先,我怀疑这与您的问题有什么关系,但我会警惕使用nullptrandTRUE作为参数来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];

在非主线程路径中?

于 2012-12-14T05:04:25.907 回答
0

我找到了罪魁祸首。问题是由于另一个应用程序起诉了 MacOS 的 Accessibility API。当显示模式对话框时,Java 主框架不响应所需的本机辅​​助功能 API 调用,这会挂起整个应用程序。避免本机模式对话框,解决了这个问题。

例如,对于 NSOpenPanel,我们应该更改[panel runModal][panel beginSheetModalForWindow]. 这解决了问题。

于 2019-04-12T16:45:23.477 回答
0

这个问题可能有更好的解决方案。

死锁是由两个线程相互等待引起的:AppKit 主线程和 AWT 事件线程。调用本机方法的 Java 代码正在 AWT 事件线程上运行。本机方法在等待创建 NSOpenPanel 的代码在 AppKit 主线程上执行时阻塞 AWT 事件线程。同时,其他事件可能会在 AppKit 主线程上处理,如果这些事件涉及 AWT 窗口,则可能会向上调用 Java。由于 Java 更喜欢使用 AWT 在其 UI 上进行操作,因此向上调用可能会在 AWT 事件队列上发布一个事件并阻塞等待该事件被处理。但是只要 AWT 事件线程在您的本机方法中被阻塞,就永远不会处理该事件。因此,陷入僵局。

解决方案是运行从新 Java 线程调用本机方法的 Java 代码,以便阻止此后台线程而不是 AWT 事件线程。如果您的代码必须在需要此计算结果的 AWT 事件线程上运行,请让它创建一个 java.awt.SecondaryLoop 并运行该循环,同时等待调用您的本机方法的线程接收结果。SecondaryLoop 将处理由 upcalls 发布的事件,从而允许 AppKit 主线程继续工作,并最终创建您的 NSOpenPanel。SecondaryLoop 的 javadoc 有一个如何执行此操作的示例。

于 2020-09-05T03:36:55.823 回答