如何取消loadTransferable(type:)?

问题描述 投票:0回答:1

我想在 SwiftUI 中实现从 PhotosPicker 加载图像的超时,以防没有互联网连接并且图像在设备上尚未提供全分辨率。

为此,我使用了带有并行计时器任务的任务组,但不幸的是,

loadTransferable(type:)
似乎不处理取消。一旦计时器任务完成并且调用
group.cancelAll()
,图像加载任务就不会完成,因此根本不会进入
switch
块。

相反的方式确实有效:如果我选择设备上可用的图像,则计时器任务将被取消并且任务组完成。

我做错了什么吗?有解决方法或其他方法吗?

func loadImage(_ image: PhotosPickerItem, timeout milliseconds: Int) async throws -> UIImage {
    var result: Result<UIImage, Error>!
    
    await withTaskGroup(of: Result<UIImage, Error>.self) { group in
        // Load the image.
        group.addTask {
            do {
                if let imageData = try await image.loadTransferable(type: Data.self) {
                    if let image = UIImage(data: imageData) {
                        return .success(image)
                    }
                }
                return .failure(MyError.imageCouldNotBeLoaded(details: "Image transfer or decoding issues."))
            } catch {
                return .failure(MyError.imageCouldNotBeLoaded(details: "Unsupported image format."))
            }
        }
        
        // Activate the timeout timer.
        group.addTask {
            do {
                try await Task.sleep(nanoseconds: UInt64(milliseconds * 1_000_000))
                return .failure(MyError.imageCouldNotBeLoaded(details: "Timeout occured."))
            } catch is CancellationError {
                return .failure(MyError.loadingCancelled)
            } catch {
                return .failure(error)
            }
        }
        
        // Wait for the first result and stop all (other) tasks.
        if let taskResult = await group.next() {
            result = taskResult
            group.cancelAll()
        }
    }

    switch result {
    case .success(let image):
        return image
    case .failure(let error):
        throw error
    case .none:
        throw MyError.imageCouldNotBeLoaded(details: "Task not executed or cancelled.")
    }
}
swift async-await task photospicker
1个回答
0
投票

由于我怀疑访问变量存在数据争用问题

result
,因此此实现将简化您的代码并且不存在数据争用问题,请参见下文。

但是,如果

image.loadTransferable(type:)
不处理取消,这并不能解决您的问题。任务组需要等待所有子任务完成。

private func loadImage(
    _ image: PhotosPickerItem,
    timeout milliseconds: UInt64
) async throws -> UIImage {
    try await withThrowingTaskGroup(
        of: UIImage.self,
        returning: UIImage.self
    ) { group in
        // Load the image.
        group.addTask {
            let data = try await self.loadTransferable(photosPickerItem: image)
            guard Task.isCancelled == false,
                    let imageData = data,
                    let image: UIImage = UIImage(data: imageData)
            else {
                throw MyError.imageCouldNotBeLoaded(
                    details: Task.isCancelled ? "cancelled"
                    : data == nil ? "content type not supported" : "image data encoding error"
                )
            }
            return image
        }
        
        // Activate the timeout timer.
        group.addTask {
            try await Task.sleep(for: .milliseconds(milliseconds))
            throw MyError.loadingCancelled
        }
        
        // If a child task throws an error and if it will be propagated from
        // this method out of the body of this function, then all remaining
        // child tasks in that group are implicitly canceled.
        guard let image = try await group.next() else {
            fatalError("can't happen") // `group.next()` either throws or it returns an image.
        }
        return image
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.