GCD上的NSOpenPanel.runModal + NSAlert.runModal导致挂起

问题描述

在我的Cocoa应用程序中,我在后台进行了一些计算。后台工作由DispatchQueue.global(qos: .utility).async运行。 此后台任务可能通过通过NSAlert显示模式DispatchQueue.main.async来报告错误。

此外,在我的应用程序中,用户可以运行NSOpenPanel打开某些文件(使用NSOpenPanel.runModal)。

问题在于,如果用户打开NSOpenPanel,同时后台任务显示NSAlert,则应用程序可能会挂起。

  • 用户打开模式NSOpenPanel
  • 后台任务在NSAlert上方打开模式NSOpenPanel
  • 用户点击NSOpenPanel内的 Close (尽管存在更多模式NSOpenPanel,它实际上仍可以访问NSAlert)。
  • NSAlertNSOpenPanel都被关闭,应用程序挂起,并且主线程在NSOpenPanel.runModal()内部被阻塞
  • 如果用户先关闭NSAlert然后关闭NSOpenPanel
  • 应用程序将不会挂起。

最小代码示例(将测试IBaction绑定为主窗口中按钮的操作)

import Cocoa

@NSApplicationMain
class AppDelegate: NSObject,NSApplicationDelegate {

    @IBOutlet weak var window: NSWindow!
   
    @IBAction func test(_ sender: Any) {
        //run some work in background
        DispatchQueue.global(qos: .utility).async
        {
            sleep(1) //some work

            //report errors in the main thread.
            DispatchQueue.main.async {
                let alert = NSAlert();
                alert.informativeText = "Close NSOpen panel before this alert to reproduct the hang."
                alert.runModal()
            }
        }
        
        //user want to open a file and opens the open file dialog
        let dlg = NSOpenPanel();
        dlg.runModal();
    }
}

那么,这段代码有什么问题,为什么在特定的用例中它会导致挂起?以及如何防止这种死机?

补充说明:我发现,然后用dlg.runModal()替换NSApp.RunModal(for: dlg)(与Apple文档完全相同),这将解决上述用例中的问题。 但是它仍然会在关闭NSAlert之后立即自动关闭NSOpenPanel。而且我仍然不明白为什么这样做会如此。

更新

我更新了上面的代码,以包含最小可复制应用程序的AppDelegate类的完整代码。要重现此案例,只需在XCode中创建新的SwiftApp,替换AppDelegate代码,在主窗口中添加按钮,并使用test func来绑定按钮的动作。我还在github上放置了完整的可编译项目:https://github.com/snechaev/hangTest

排除了用于配置NSOpenPanel和NSAlert及其结果处理的代码,因为此类代码不会影响挂起。

解决方法

暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!

如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。

小编邮箱:dio#foxmail.com (将#修改为@)