`DispatchQueue.main` not working (on Windows)
s-k opened this issue · 4 comments
Describe the bug
Closures passed to DispatchQueue.main.async
will never be called. Similarly, Swift Concurrency Task
s scheduled to run on @MainActor
are never called.
To Reproduce
These print statements are never executed when using Adwaita for Swift on Windows:
DispatchQueue.main.async {
print("Hi!") // Never called
}
Task { @MainActor in
print("Hi, too!") // Never called
}
Expected behavior
I expect that the Main Dispatch Queue and the Main Actor can be used normally. Alternatively, the library should surface a similar API to run code on the Main Thread.
Additional context
I assume, to make this work, the GTK run loop must be integrated with libdispatch. Maybe this can be helpful: https://stackoverflow.com/questions/10291972/integrating-a-custom-run-loop-with-libdispatch
Thanks for opening the issue! I will look into this at some point. Currently, you can use Idle which calls GLib.idle_add to add functions to the main context (e.g. for updating the UI).
@david-swift Thanks for your response! Interestingly, I have been working on a workaround and stumbled on g_idle_add
myself.
Here is my workaround for anyone interested. It works but is obviously not optimal:
import CAdw
import Foundation
@globalActor
public struct MainActor {
public actor Actor {
public nonisolated var unownedExecutor: UnownedSerialExecutor {
Executor.sharedUnownedExecutor
}
}
final class Executor: SerialExecutor {
static var sharedUnownedExecutor: UnownedSerialExecutor = UnownedSerialExecutor(ordinary: Executor())
private class JobWrapper {
let job: UnownedJob
init(_ job: consuming ExecutorJob) {
self.job = UnownedJob(job)
}
}
private init() {}
func enqueue(_ job: consuming ExecutorJob) {
let wrappedJob = JobWrapper(job)
g_idle_add({ jobPointer in
let job = Unmanaged<JobWrapper>.fromOpaque(jobPointer!).takeRetainedValue().job
job.runSynchronously(on: MainActor.Executor.sharedUnownedExecutor)
return 0
}, Unmanaged.passRetained(wrappedJob).toOpaque())
}
func asUnownedSerialExecutor() -> UnownedSerialExecutor {
UnownedSerialExecutor(ordinary: self)
}
}
public static let shared = Actor()
@available(*, noasync)
public static func assumeIsolated<T>(
_ operation: @MainActor () throws -> T,
file: StaticString = #fileID,
line: UInt = #line
) rethrows -> T {
guard Thread.isMainThread else {
fatalError("`MainActor.assumeIsolated` was called from a non-main thread", file: file, line: line)
}
typealias UnconstrainedOperation = () throws -> T
return try withoutActuallyEscaping(operation) { (operation) -> T in
let unconstrainedOperation = unsafeBitCast(operation, to: UnconstrainedOperation.self)
return try unconstrainedOperation()
}
}
}
This creates a new MainActor
which can be used as a replacement for the built-in MainActor
to make Swift Concurrency work with Adwaita for Swift on Windows.