AppKit events failing on main queue

`So I am attempting to use GLFW with Swift to create a very simple application, and I am running into an issue I don't understand with AppKit.


Currently the application should only create a window and then poll input on a timer. My `main.swift` looks like this:


//main.swift

// GLFW is imported via bridging header
import Foundation

glfwInit()

let window = glfwCreateWindow(400, 400, "window", nil, nil)

let timer = DispatchSource.makeTimerSource()
timer.schedule(deadline: DispatchTime.now() + 1.0, repeating: 1.0, leeway: DispatchTimeInterval.nanoseconds(10))
timer.setEventHandler {
    DispatchQueue.main.sync {
        print("work work work")
        glfwPollEvents()
    }
}
timer.resume()

dispatchMain()

So the problem is, when I run this application, I get the following fatal exception:


Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'nextEventMatchingMask should only be called from the Main Thread!'


What's strange is, this exception is resulting from the following line being executed from within `glfwPollEvents`:


        NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny
                                            untilDate:[NSDate distantPast]
                                               inMode:NSDefaultRunLoopMode
                                              dequeue:YES];


I thought this should be safe, siunce it is being called from within a `DispatchQueue.main.sync` block, but apparently the main queue does not guarantee that the main thread is being used as I am also getting this warning from GLFW:


Attempting to perform block on main runloop, but the main thread as exited. This message will only log once. Break on _CFRunLoopError_MainThreadHasExited to debug.


So my questions are:


1. What is the difference between the main queue and the main thread, and


2. What is the correct way to execute timed repeating work on the main thread

A queue is a data structure for organizing tasks (a.k.a. work items). A thread is an execution context. Threads are used to run those tasks submitted to queues. Threads can also be used for other things. Most queues do not have dedicated threads. The system maintains a pool of worker threads that take tasks from the queues and execute them. It adds and terminates threads as necessary.


The main queue is associated with the main thread, but, as you've seen, that doesn't necessarily means that tasks submitted to the main queue run on the main thread. In particular, when you submit a task synchronously (with sync()), the task typically runs on the submitting thread. After all that thread can't do anything else, since it would have to wait for the task to complete anyway. It's already scheduled on the CPU, so it just runs the task itself.


One way to "fix" the issue you've encountered is to submit the task asynchronously, but that's not really necessary.


The better way is to specify the main queue when you create the timer source:


let timer = DispatchSource.makeTimerSource(queue:DispatchQueue.main)


Then, just do the work directly in the event handler without dispatching it to a queue again:


timer.setEventHandler {
    print("work work work")
    glfwPollEvents()
}
AppKit events failing on main queue
 
 
Q