improve concurrency

This commit is contained in:
mertalev
2026-02-02 01:52:38 -05:00
parent 2e911ec1ca
commit 69a50e9f7e
3 changed files with 50 additions and 38 deletions

View File

@@ -0,0 +1,7 @@
import Foundation
enum ImageProcessing {
static let queue = DispatchQueue(label: "thumbnail.processing", qos: .userInitiated, attributes: .concurrent)
static let semaphore = DispatchSemaphore(value: ProcessInfo.processInfo.activeProcessorCount * 2)
static let cancelledResult = Result<[String: Int64]?, any Error>.success(nil)
}

View File

@@ -34,7 +34,6 @@ class LocalImageApiImpl: LocalImageApi {
private static let assetQueue = DispatchQueue(label: "thumbnail.assets", qos: .userInitiated)
private static let requestQueue = DispatchQueue(label: "thumbnail.requests", qos: .userInitiated)
private static let cancelQueue = DispatchQueue(label: "thumbnail.cancellation", qos: .default)
private static let processingQueue = DispatchQueue(label: "thumbnail.processing", qos: .userInteractive, attributes: .concurrent)
private static var rgbaFormat = vImage_CGImageFormat(
bitsPerComponent: 8,
@@ -44,8 +43,6 @@ class LocalImageApiImpl: LocalImageApi {
renderingIntent: .defaultIntent
)!
private static var requests = [Int64: LocalImageRequest]()
private static let cancelledResult = Result<[String: Int64]?, any Error>.success(nil)
private static let concurrencySemaphore = DispatchSemaphore(value: ProcessInfo.processInfo.activeProcessorCount * 2)
private static let assetCache = {
let assetCache = NSCache<NSString, PHAsset>()
assetCache.countLimit = 10000
@@ -53,7 +50,7 @@ class LocalImageApiImpl: LocalImageApi {
}()
func getThumbhash(thumbhash: String, completion: @escaping (Result<[String : Int64], any Error>) -> Void) {
Self.processingQueue.async {
ImageProcessing.queue.async {
guard let data = Data(base64Encoded: thumbhash)
else { return completion(.failure(PigeonError(code: "", message: "Invalid base64 string: \(thumbhash)", details: nil)))}
@@ -71,16 +68,16 @@ class LocalImageApiImpl: LocalImageApi {
let request = LocalImageRequest(callback: completion)
let item = DispatchWorkItem {
if request.isCancelled {
return completion(Self.cancelledResult)
return completion(ImageProcessing.cancelledResult)
}
Self.concurrencySemaphore.wait()
ImageProcessing.semaphore.wait()
defer {
Self.concurrencySemaphore.signal()
ImageProcessing.semaphore.signal()
}
if request.isCancelled {
return completion(Self.cancelledResult)
return completion(ImageProcessing.cancelledResult)
}
guard let asset = Self.requestAsset(assetId: assetId)
@@ -91,7 +88,7 @@ class LocalImageApiImpl: LocalImageApi {
}
if request.isCancelled {
return completion(Self.cancelledResult)
return completion(ImageProcessing.cancelledResult)
}
var image: UIImage?
@@ -116,7 +113,7 @@ class LocalImageApiImpl: LocalImageApi {
}
if request.isCancelled {
return completion(Self.cancelledResult)
return completion(ImageProcessing.cancelledResult)
}
do {
@@ -124,7 +121,7 @@ class LocalImageApiImpl: LocalImageApi {
if request.isCancelled {
buffer.free()
return completion(Self.cancelledResult)
return completion(ImageProcessing.cancelledResult)
}
request.callback(.success([
@@ -143,7 +140,7 @@ class LocalImageApiImpl: LocalImageApi {
request.workItem = item
Self.add(requestId: requestId, request: request)
Self.processingQueue.async(execute: item)
ImageProcessing.queue.async(execute: item)
}
func cancelRequest(requestId: Int64) {
@@ -164,7 +161,7 @@ class LocalImageApiImpl: LocalImageApi {
request.isCancelled = true
guard let item = request.workItem else { return }
if item.isCancelled {
cancelQueue.async { request.callback(Self.cancelledResult) }
cancelQueue.async { request.callback(ImageProcessing.cancelledResult) }
}
}
}

View File

@@ -19,7 +19,6 @@ class RemoteImageRequest {
class RemoteImageApiImpl: NSObject, RemoteImageApi {
private static var lock = os_unfair_lock()
private static var requests = [Int64: RemoteImageRequest]()
private static let cancelledResult = Result<[String: Int64]?, any Error>.success(nil)
private static var rgbaFormat = vImage_CGImageFormat(
bitsPerComponent: 8,
bitsPerPixel: 32,
@@ -64,45 +63,54 @@ class RemoteImageApiImpl: NSObject, RemoteImageApi {
if let error = error {
if request.isCancelled || (error as NSError).code == NSURLErrorCancelled {
return request.completion(cancelledResult)
return request.completion(ImageProcessing.cancelledResult)
}
return request.completion(.failure(error))
}
if request.isCancelled {
return request.completion(cancelledResult)
return request.completion(ImageProcessing.cancelledResult)
}
guard let data = data else {
return request.completion(.failure(PigeonError(code: "", message: "No data received", details: nil)))
}
guard let imageSource = CGImageSourceCreateWithData(data as CFData, nil),
let cgImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, decodeOptions) else {
return request.completion(.failure(PigeonError(code: "", message: "Failed to decode image for request", details: nil)))
}
if request.isCancelled {
return request.completion(cancelledResult)
}
do {
let buffer = try vImage_Buffer(cgImage: cgImage, format: rgbaFormat)
ImageProcessing.queue.async {
ImageProcessing.semaphore.wait()
defer { ImageProcessing.semaphore.signal() }
if request.isCancelled {
buffer.free()
return request.completion(cancelledResult)
return request.completion(ImageProcessing.cancelledResult)
}
request.completion(
.success([
"pointer": Int64(Int(bitPattern: buffer.data)),
"width": Int64(buffer.width),
"height": Int64(buffer.height),
"rowBytes": Int64(buffer.rowBytes),
]))
} catch {
return request.completion(.failure(PigeonError(code: "", message: "Failed to convert image for request: \(error)", details: nil)))
guard let imageSource = CGImageSourceCreateWithData(data as CFData, nil),
let cgImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, decodeOptions) else {
return request.completion(.failure(PigeonError(code: "", message: "Failed to decode image for request", details: nil)))
}
if request.isCancelled {
return request.completion(ImageProcessing.cancelledResult)
}
do {
let buffer = try vImage_Buffer(cgImage: cgImage, format: rgbaFormat)
if request.isCancelled {
buffer.free()
return request.completion(ImageProcessing.cancelledResult)
}
request.completion(
.success([
"pointer": Int64(Int(bitPattern: buffer.data)),
"width": Int64(buffer.width),
"height": Int64(buffer.height),
"rowBytes": Int64(buffer.rowBytes),
]))
} catch {
return request.completion(.failure(PigeonError(code: "", message: "Failed to convert image for request: \(error)", details: nil)))
}
}
}