Post

Replies

Boosts

Views

Activity

Reply to Image Caching and Actors
Is there any state that's being read/written to in the ImageProcessing actor? If not then I don't know if you should use an actor at all unless whatever processImageData does requires exclusive execution. As for calling that code on the background from the main actor, you need to use asyncDetached because regular async will inherit the actor from the current execution context (the MainActor in this case). I think this is the code you're aiming for: @MainActor class OsuImageCache { init() { ... } /// Grab an image on the main thread for use in collection view cells func image(for url: URL) -> UIImage? { } /// Cache an image on a background thread func cacheImage(for url: URL) async throws -> (image: UIImage?, thumbnail: UIImage?) { let (data, _) = try await session.data(from: url) let processTask = asyncDetached(priority: /* use whatever you want like .userInitiated or .utility */) { return processImageData(data, for url) } let processedImages = await processTask.get() try saveInFileSystem(imageData: processedImages.imageData, thumbnailImageData: processedImages.thumbnailData, for url: URL) return (UIImage(data: processedImages.imageData), UIImage(data: processedImages.thumbnailData)) } /// Resize images and create thumbnails /// NOT ASYNC func processImageData(_ data: Data, url: URL) -> (UIImage, UIImage) { // Code that resizes the image and the thumbnail goes here. It's all inline, no completion handlers nor async calls... ... // Finally, return return (finalProcessedImageData, finalProcessedThumbnailData) } }
Jun ’21
Reply to Reentrance and assumptions after suspension point
No problem. πŸ™Œ Yes, concurrency will always be complex and devs will need to take care when using it. The real awesome benefits of async/await and actors are better syntax and compile time checks against the lower-level problems of data corruption that are super-hard to find. Trying to reason about the behavior you asked about would be 10x harder in a callback-based implementation. 😡
Jun ’21
Reply to Combining Actors with blazing fast lists
First of all, I'd cache the prepared the thumbnail, possibly in a separate cache or make your asset store layered in a way that you just request a URL and it will supply a prepared image or load + prepare it for you. With separate caches, you can restrict the sizes separately (look into using NSCache for this). Secondly, a call being async doesn't mean it will suspend 100% of the time when called. For instance, if your asset(forURL:) function doesn't have to await in the code path that returns .image, then the call is essentially synchronous. Similarly, you should mark this view controller and cell as @MainActor so you can remove the dispatch async to main within your async {}. The task will inherit the @MainActor from the context that creates the task. To get the behavior you want, you need to hold onto the task handle that async {} returns so you can cancel it when the cell is reused.
Jun ’21
Reply to Reentrance and assumptions after suspension point
Assuming you're referring to the code from this post: https://developer.apple.com/forums/thread/682032 This code works because of the checks at the beginning that are omitted in your quote. Only a call to this function that has read cache[url] as nil can start down the code path to write to the internal state. While a call is suspended at let image = try await handle.get(), any other call for the same URL will read .inProgress from the cache and return/throw the task handle's result without writing to the internal state. There's no suspension points between reading the current state and writing something based on that state. reading .inProgress or .ready will not cause that call to write to internal state. reading nil and writing .inProgress happen within the same uninterrupted execution. After suspending, this context will overwrite its own earlier value of .inProgress with either .ready or nil. This is valid for the code as it is written, if we wanted to be extra-safe we could re-read and make sure the state for that URL is still .inProgress with the same handle. That's not necessary for this code, but adding other functions that manipulate the actor's state (e.g. adding a deleteCache(for: URL) method) would require careful updates to make sure this stays correct.
Jun ’21