首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >GCD上的NSOpenPanel.runModal + NSAlert.runModal导致挂起

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

Stack Overflow用户
提问于 2020-08-26 08:25:51
回答 2查看 619关注 0票数 1

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

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

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

用户打开模式NSOpenPanel

  • background任务在NSOpenPanel

  • user单击Close if NSOpenPanel (它确实可以访问NSOpenPanel,尽管关闭了更多的模式,present).

  • both NSAlertNSOpenPanel,应用程序挂起主线程阻止在NSOpenPanel.runModal()

  • application中,如果用户首先关闭NSAlert,然后NSOpenPanel.

)不会挂起

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

代码语言:javascript
运行
复制
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) (这与苹果文档完全相同),这将修复上面描述的usecase中的挂起问题。但是在关闭NSAlert之后,它仍然会自动关闭NSOpenPanel。我还是不明白为什么会这样。

更新

我更新了上面的代码,以包含最小可复制应用程序的AppDelegate类的完整代码。要重现这种情况,只需在XCode中创建新的XCode,替换AppDelegate代码,在主窗口中添加按钮,用test func连接按钮的操作。我还将完整的准备编译项目放在github:https://github.com/snechaev/hangTest上。

配置NSOpenPanel和NSAlert的代码及其结果处理被排除在外,因为这样的代码不会影响挂起。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2020-08-29 17:56:18

我认为您是在死锁主队列,因为runModal()同时在代码的两个位置阻塞主队列。如果您的代码是可重入的,这就是您得到的。

可能的解决办法:

  1. 避免使用app模式窗口,而使用窗口模式窗口。单张.若要了解如何使用NSOpenPanel作为工作表,请将其附加到与之相关的窗口。例如,请参见以下答案:

NSOpenPanel as sheet

  1. 您可以设置一个标志,以防止用户在显示警报时打开NSOpenPanel,但这很难看,并且不会解决任何可能导致其他死锁的问题,因为很可能您的代码是可重入的。
票数 1
EN

Stack Overflow用户

发布于 2020-08-31 09:13:14

除了@jvarela之外,我还想添加一些细节,并就我的问题做一些简历。

  1. 似乎无法解决将NSPanel/NSAlert作为模式窗口并阻塞调用方线程(使用runModal)的问题。
  2. 非模态(NSPanel.begin())或模态非阻塞(NSPanel.beginSheet,NSPanel.beginSheetModal)不会导致挂起,但如果用户试图在NSAlert之前关闭NSPanel,仍然会导致NSAlert的意外自动关闭。此外,要使用这种非阻塞方法,您将不得不重构整个代码基,在使用NSPanel时使用回调/完成处理程序而不是阻塞操作。
  3. I没有找到为什么NSPanel没有被阻塞的原因,并在它的顶部显示模态NSAlert时继续接收用户输入。我怀疑这是因为安全机制在单独的进程中运行NSPanel,但我没有证据证明这一点。我仍然对此感兴趣。对于我当前的项目,
  4. 决定将阻塞方式留给NSPanel使用,因为我有很大的代码库,很难立即将其更改为使用完成处理程序。对于NSPanel + NSAlert的特殊情况,我只是不允许用户在此特定背景工作期间打开NSPanel。用户现在应该等待后台工作完成,或者手动取消工作,以便能够运行Open functionality.
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/63593563

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档