DispatchQueue.main not working (on Windows) #38

Open
opened 2024-06-25 12:23:22 +02:00 by s-k · 3 comments
s-k commented 2024-06-25 12:23:22 +02:00 (Migrated from github.com)

Describe the bug

Closures passed to DispatchQueue.main.async will never be called. Similarly, Swift Concurrency Tasks 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

### 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: ```swift DispatchQueue.main.async { print("Hi!") // Never called } ``` ```swift 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
david-swift commented 2024-06-26 11:09:15 +02:00 (Migrated from github.com)

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).

Thanks for opening the issue! I will look into this at some point. Currently, you can use [Idle](https://aparokshaui.github.io/adwaita-swift/documentation/adwaita/idle) which calls [GLib.idle_add](https://docs.gtk.org/glib/func.idle_add.html) to add functions to the main context (e.g. for updating the UI).
s-k commented 2024-06-26 12:37:41 +02:00 (Migrated from github.com)

@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.

@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: ```swift 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.
biscuitehh commented 2024-08-12 11:12:12 +02:00 (Migrated from github.com)

@s-k just wanted to chime in - I'm working on a Linux app w/ Swift 6 concurrency and your MainActor bits unblocked me. Thanks for sharing!

@s-k just wanted to chime in - I'm working on a Linux app w/ Swift 6 concurrency and your `MainActor` bits unblocked me. Thanks for sharing!
david-swift added the
bug
label 2024-10-07 12:15:33 +02:00
Sign in to join this conversation.
No Milestone
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: aparoksha/adwaita-swift#38
No description provided.