Skip to content

Instantly share code, notes, and snippets.

@brennanMKE
Created October 10, 2023 20:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save brennanMKE/ebfc1c834758419ef2e434b2e529df5c to your computer and use it in GitHub Desktop.
Save brennanMKE/ebfc1c834758419ef2e434b2e529df5c to your computer and use it in GitHub Desktop.
Blocking code to integrate Swift Concurrency with legacy code which cannot be async
/// Sometimes it is necessary to integrate straight-line code with async code
/// and this can be done using a semaphore. Keep in mind that doing this breaks
/// the Forward Progress policy that is required by Swift Concurrency.
///
/// Warning: This code is not recommended for a production environment.
import Foundation
struct Blocker<Success, Failure: Error> {
private class ResultStore {
var result: Result<Success, Failure>? = nil
}
func run(operation: @escaping () async throws -> Success) -> Result<Success, Failure> {
let store = ResultStore()
let semaphore = DispatchSemaphore(value: 0)
Task {
do {
let value = try await operation()
store.result = .success(value)
} catch {
if let failure = error as? Failure {
store.result = .failure(failure)
} else {
fatalError("Error type not supported: \(error)")
}
}
semaphore.signal()
}
semaphore.wait()
return store.result!
}
}
struct Worker {
enum Failure: Error {
case numberTooHigh
}
func process(number: Int) async throws -> Int {
if number < 10 {
number * 2
} else {
throw Failure.numberTooHigh
}
}
}
let doAsync = false
let worker = Worker()
if doAsync {
print("Async:")
Task {
for number in 1...10 {
do {
let output = try await worker.process(number: number)
print("\(number) -> \(output)")
} catch {
if let failure = error as? Worker.Failure {
switch failure {
case .numberTooHigh:
print("Error: Number was too high (\(number))")
}
}
}
}
print("Done")
}
} else {
print("Blocking:")
let blocker = Blocker<Int, Worker.Failure>()
for number in 1...10 {
let result = blocker.run {
try await worker.process(number: number)
}
do {
let output = try result.get()
print("\(number) -> \(output)")
} catch {
if let failure = error as? Worker.Failure {
switch failure {
case .numberTooHigh:
print("Error: Number was too high (\(number))")
}
}
}
}
print("Done")
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment