Can anyone tell me why NSAlert.runModal() is crashing when called from inside the Task.init {} function below.
Thread 1: EXC_BREAKPOINT (code=1, subcode=0x1a4122a18)
The logs report that this is being run on the main thread so why would this crash ?
If I wrap it in DispatchQueue.main.async { } then it works fine.
The isBusy property is bound to the enabled state of the start button and this does not complain about being called from a background thread.
Is this a bug or am I doing something fundamentally wrong with my use of Task.init {} to wrap async functions in a viewController ?
class OSViewController: NSViewController {
@IBOutlet weak var ringBar: OSPlainCircularProgressBar!
@IBOutlet weak var upBar: OSProgressBar!
@IBOutlet weak var downBar: OSProgressBar!
@objc dynamic var progress: Double = 0.0 {
didSet {
ringBar.progress = progress / 100.0
upBar.progress = progress / 100.0
downBar.progress = progress / 100.0
}
}
@objc dynamic var isBusy = false
let asyncObj = AsyncObject(name: "Some Object")
var progressCallback: (Double)->Void {
return {progress in
DispatchQueue.main.async { self.progress = progress }
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
@IBAction func start(_ sender: Any) {
isBusy = true
log("start")
Task.init {
log("sync start")
let (result, message) = await asyncObj.process(5, progress: progressCallback)
isBusy = false
log(message)
log("sync end") // Reports as running in main thread
// CRASHES HERE !
let alert = self.dialogOK(question: "Some question", text: "Some thing else")
log("task end") // Reports as running in main thread
}
}
func dialogOK(question: String, text: String) -> Bool {
let alert = NSAlert()
alert.messageText = question
alert.informativeText = text
alert.alertStyle = .warning
alert.addButton(withTitle: "OK")
return alert.runModal() == .alertFirstButtonReturn
}
/// This process MUST run on a background thread - it cannot run on the main thread
/// So how do I force it to run on a background thread ?
func process(_ count: Int, progress: @escaping (Double)->Void) async -> (Bool, String) {
// Still on the main thread here ??
log("1 First part on the main thread ??")
let task = Task.detached { () -> (Bool, String) in
//Thread.sleep(forTimeInterval: 0.1)
log("2 Run a loop in the background")
log(" Thread: \(Thread.isMainThread ? "UI" : "BG")")
var cnt = 0
for i in 0..<count {
Thread.sleep(forTimeInterval: 0.025)// sleep(seconds: 0.1)
log(" i: \(i)")
cnt += 1
progress (Double(cnt)/Double(count)*100)
}
log(" >>>>")
return (true, "Background task Done")
}
let result = await task.value
log("2 First part on the main thread ??")
return result
}
}
func log(_ msg: String) {
let type = Thread.isMainThread ? "UI" : "BG"
print("\(type): \(msg)")
}
extension OSViewController {
func sleep(seconds: Double) async {
await Task.sleep(UInt64(seconds * 1000000000.0))
}
}
class AsyncObject {
var name: String = "Someobject"
init(name: String){
self.name = name
}
/// This process MUST run on a background thread - it cannot run on the main thread
/// So how do I force it to run on a background thread ?
@MainActor func process(_ count: Int, progress: @escaping (Double)->Void) async -> (Bool, String) {
// Still on the main thread here ??
log("1 First part on the main thread ??")
log(" Object is \(self.name)")
let task = Task.detached { () -> (Bool, String) in
//Thread.sleep(forTimeInterval: 0.1)
log("2 Run a loop in the background")
log(" Thread: \(Thread.isMainThread ? "UI" : "BG")")
var cnt = 0
for i in 0..<count {
Thread.sleep(forTimeInterval: 0.025)// sleep(seconds: 0.1)
log(" i: \(i)")
cnt += 1
progress (Double(cnt)/Double(count)*100)
}
log(" >>>>")
return (true, "Background task Done")
}
let result = await task.value
log("2 First part on the main thread ??")
return result
}
}
EDIT: This seems to work
@IBAction func start(_ sender: Any) {
isBusy = true
log("start")
Task.init {
log("sync start")
let (result, message) = await asyncObj.process(5, progress: progressCallback)
isBusy = false
log(message)
log("sync end") // Reports as running in main thread
// NO CRASHES HERE !
self.alert = self.dialogOK(question: "Some question", text: "Some thing else")
if let win = self.view.window {
self.alert?.beginSheetModal(for: win) {result in
self.alert = nil
}
}
log("task end") // Reports as running in main thread
}
}