Concurrency 101

Connecting the sync world with the async world.

@Gero posted an example in this thread, which does away with the need to use semaphores when connecting the sync world with the async world.

I have adapted the following example from it to demonstrate its usefulness.

@main
enum sync_async {
    static func main () throws {
        let sa = SyncAsyncAdaptor ()
        sa.enqueue (f)
        sa.enqueue (g)
        sa.enqueue (h)
        
        #if false
        Thread.sleep (until: .now + 19)
        #endif
        
        dispatchMain()
    }
}

func f () async {
    print (#function, "begin...")
    try! await Task.sleep (until: .now + .seconds(7))
    DispatchQueue.main.async {
        print (#function, 1)
    }
    print (#function, "end.")
}

func g () async {
    print (#function, "begin...")
    try! await Task.sleep (until: .now + .seconds(10))
    DispatchQueue.main.async {
        print (#function, 2)
    }
    print (#function, "end.")
}

func h () async {
    DispatchQueue.main.async {
        // cause the program to exit
        print (#function, "exit...")
        exit (0)
    }
}

final class SyncAsyncAdaptor {
    private let continuation: AsyncStream<() async -> Void>.Continuation

    init() {
        let (stream, cont) = AsyncStream<() async -> Void>.makeStream()
        continuation = cont
        Task.detached {
            for await workItem in stream {
                await workItem ()
            }
        }
    }

    deinit {
        continuation.finish()
    }

    func enqueue (_ workItem: @escaping () async -> Void) {
        continuation.yield (workItem)
    }
}

Produces the output:

f() begin...
f() end.
g() begin...
f() 1
g() end.
g() 2
h() exit...
Program ended with exit code: 0
1 Like