在我的可可应用程序中,我在后台做了一些计算。后台工作由DispatchQueue.global(qos: .utility).async运行。此后台任务可能通过通过NSAlert显示模态DispatchQueue.main.async来报告错误。
此外,在我的应用程序中,用户可以运行NSOpenPanel来打开一些文件(使用NSOpenPanel.runModal)。
问题是,如果用户打开NSOpenPanel,同时后台任务显示NSAlert,则应用程序可能挂起。
用户打开模式NSOpenPanel
NSOpenPanel
NSOpenPanel (它确实可以访问NSOpenPanel,尽管关闭了更多的模式,present).
NSAlert和NSOpenPanel,应用程序挂起主线程阻止在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) (这与苹果文档完全相同),这将修复上面描述的usecase中的挂起问题。但是在关闭NSAlert之后,它仍然会自动关闭NSOpenPanel。我还是不明白为什么会这样。
更新
我更新了上面的代码,以包含最小可复制应用程序的AppDelegate类的完整代码。要重现这种情况,只需在XCode中创建新的XCode,替换AppDelegate代码,在主窗口中添加按钮,用test func连接按钮的操作。我还将完整的准备编译项目放在github:https://github.com/snechaev/hangTest上。
配置NSOpenPanel和NSAlert的代码及其结果处理被排除在外,因为这样的代码不会影响挂起。
发布于 2020-08-29 17:56:18
我认为您是在死锁主队列,因为runModal()同时在代码的两个位置阻塞主队列。如果您的代码是可重入的,这就是您得到的。
可能的解决办法:
NSOpenPanel作为工作表,请将其附加到与之相关的窗口。例如,请参见以下答案:NSOpenPanel,但这很难看,并且不会解决任何可能导致其他死锁的问题,因为很可能您的代码是可重入的。发布于 2020-08-31 09:13:14
除了@jvarela之外,我还想添加一些细节,并就我的问题做一些简历。
https://stackoverflow.com/questions/63593563
复制相似问题