Hello.
In some protocol requirements, and when dealing with some old APIs such as AVFoundation
, it may come to the edge case where you will have to use an async function in a sync context.
Here is my implementation, and I would like to ask some questions.
import Dispatch
func withAsyncResult<T>(builder: @escaping () async -> T) -> T {
let semaphore = DispatchSemaphore(value: 0)
let pointer = UnsafeMutablePointer<T>.allocate(capacity: 1)
Task {
await pointer.initialize(to: builder())
semaphore.signal()
}
semaphore.wait()
return pointer.pointee
}
The code runs fine, for example
class Model {
var a: Int? = nil
func update() {
self.a = withAsyncResult {
try! await Task.sleep(for: .seconds(1))
return 1
}
}
}
let date = Date()
let model = Model()
model.update()
print(model.a)
print(date.distance(to: Date()))
would print
Optional(1)
1.045375943183899
Is this a valid implementation? Will this implementation ever hang the main thread?
Yes, there is a compiler warning of Capture of 'pointer' with non-sendable type 'UnsafeMutablePointer<T>' in a
@Sendable closure
, but all it ever introduce data racing? The semaphore
should ensure the task is completed before returning.
If it does work, how is that different to this?
@MainActor
class Model {
var a: Int? = nil
nonisolated func update() async {
Task { @MainActor in
try! await Task.sleep(for: .seconds(1))
self.a = 1
}
}
}
let model = Model()
await model.update()
try await Task.sleep(for: .seconds(2))
print(model.a)
Which one is more efficient or error-prone? why?
Any suggestions would be greatly appreciated. Thank you in advance!
I understand the examples are quite weird, but in my practices of writing SwiftUI Buttons and need to call async functions, I would do something like this:
Button {
Task {
self.state = await foo()
}
}
Which is somewhat similar to the example.
Then with the new function, I could:
Button {
self.state = withAsyncResult(foo)
}
Seems to me the second one is clearer.