In my Cocoa application I have some computations done in the background. The background work is runned with DispatchQueue.global(qos: .utility).async.
This background task may report errors by showing a modal NSAlert via DispatchQueue.main.async.
Also, in my application user can run NSOpenPanel to open some files (with NSOpenPanel.runModal).
The problem is that if the user opens NSOpenPanel and at the same time the background task shows NSAlert, the application may hang.
- user opens modal
NSOpenPanel - background task opens modal
NSAlertatop theNSOpenPanel - user clicks Close inside
NSOpenPanel(it really can accessNSOpenPaneldespite the more modalNSAlertpresent). - both
NSAlertandNSOpenPanelbeing closed and application hangs with main thread blocked insideNSOpenPanel.runModal() - application will not hang if user first close
NSAlertand thenNSOpenPanel.
The minimal code sample (the test IBaction is binded as the action for the button in the main window)
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();
}
}
So, what's wrong with this code and why it cause the hang in particular use cases? And how can I prevent such a hang?
Additional note: I discovered, then if I replace dlg.runModal() with NSApp.RunModal(for: dlg) (which is exactly the same as per Apple documentation), this will fix hang in the usecase described above.
But it still will close NSAlert automatically right after closing NSOpenPanel. And I still can't understood why this behave as it does.
Update
I updated the code above to include full code of AppDelegate class of minimal reproducible application. To reproduce the case, just create new SwiftApp in XCode, replace AppDelegate code, add button in the main window and wire button's action with test func. I also placed full ready-to-compile project on github: https://github.com/snechaev/hangTest
The code for configuring NSOpenPanel and NSAlert along with their results handling is excluded as such a code does not affect the hang.