Skip to content

Instantly share code, notes, and snippets.

@Alexander-Ignition
Created April 9, 2024 08:17
Show Gist options
  • Save Alexander-Ignition/ba55efc6debe3b4f1541f596ee0deb39 to your computer and use it in GitHub Desktop.
Save Alexander-Ignition/ba55efc6debe3b4f1541f596ee0deb39 to your computer and use it in GitHub Desktop.
Swift concurrency thread executor
import Foundation
public final class ThreadExecutor: SerialExecutor, @unchecked Sendable {
private static let threadLocalName = "ThreadExecutor"
public static var current: ThreadExecutor? {
Thread.current.threadDictionary[threadLocalName] as? ThreadExecutor
}
public static func detached(
name: String,
qos: QualityOfService = .default
) async -> ThreadExecutor {
await withCheckedContinuation { continuation in
detachNewThread(name: name, qos: qos) { executor in
continuation.resume(returning: executor)
}
}
}
public static func detachNewThread(
name: String,
qos: QualityOfService = .default,
_ block: @escaping @Sendable (ThreadExecutor) -> Void
) {
Thread.detachNewThread {
let executor = ThreadExecutor(name: name, qos: qos, runLoop: .current)
let thread = Thread.current
thread.name = name
thread.qualityOfService = qos
thread.threadDictionary[Self.threadLocalName] = executor
executor.perform {
block(executor)
}
while !thread.isCancelled {
// autoreleasepool ?
executor.runLoop.run()
}
}
}
public let name: String
public let qos: QualityOfService
private let runLoop: RunLoop // not Sendable
private init(name: String, qos: QualityOfService, runLoop: RunLoop) {
self.name = name
self.qos = qos
self.runLoop = runLoop
}
public func cancel() {
perform {
Thread.current.cancel()
}
}
public func perform(_ block: @escaping @Sendable () -> Void) {
runLoop.perform(block)
}
public func enqueue(_ job: consuming ExecutorJob) {
let unownedJob = UnownedJob(job)
perform {
unownedJob.runSynchronously(on: self.asUnownedSerialExecutor())
}
}
}
extension ThreadExecutor: CustomStringConvertible {
public var description: String {
"ThreadExecutor(name: \"\(name)\", qos: \(qosDescription))"
}
private var qosDescription: String {
switch qos {
case .userInteractive:
return ".userInteractive"
case .userInitiated:
return ".userInitiated"
case .utility:
return ".utility"
case .background:
return ".background"
case .default:
return ".default"
@unknown default:
return ".unknown(\(qos.rawValue))"
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment