在 OperationQueue 中设置背压或替代 API,例如 PromiseKit、Combine Framework

问题描述

我在处理多个图像的管道中有两个步骤:

  • 第 1 步:本地加载(或下载)图像(IO 绑定)
  • 第 2 步:运行机器学习模型(cpu/GPU/计算绑定/单线程,因为模型很大)。如何限制存储在内存中的图像数量(从第 1 步开始)为第 2 步排队。这在响应式编程中称为背压

如果没有背压,第 1 步中的所有工作可能会堆积起来,导致仅打开图像就会占用大量内存。

我想我可以使用一个信号量(例如 5 个),它大致代表我愿意为第 1 步(5 张图片)提供的内存量。我想这会使我的 5 个后台线程被阻塞,这可能是件坏事? (这是一个严肃的问题:阻塞后台线程是不是不好,因为它会消耗资源。)

解决方法

如果您使用Combine,flatMap 可以提供背压。 FlatMap 为它收到的每个值创建一个发布者,但在达到指定的最大未完成发布者数时施加背压。

这是一个简化的示例。假设您有以下功能:

func loadImage(url: URL) -> AnyPublisher<UIImage,Error> {
   // ...
}

func doImageProcessing(image: UIImage) -> AnyPublisher<Void,Error> {
   // ...
}
let urls: [URL] = [...] // many image URLs

let processing = urls.publisher
    .flatMap(maxPublishers: .max(5)) { url in 
        loadImage(url: url)
           .flatMap { uiImage in
              doImageProcessing(image: uiImage)
           }
    }

在上面的例子中,它将加载 5 张图像,并开始处理它们。第 6 张图像将在较早的图像之一完成处理后开始加载。

,

如果您确实想使用 OperationQueue,只需将队列的 maxConcurrentOperationCount 设置为 5,以防止同时启动 5 个以上的操作。