使用Swift Async和循环的let变量

14

我希望能够并行获取数据。我找到了一个调用API的并行示例,但是我想在循环中存储async let变量

async let 示例。不过,这个示例没有使用循环。

async let firstPhoto = downloadPhoto(named: photoNames[0])
async let secondPhoto = downloadPhoto(named: photoNames[1])
async let thirdPhoto = downloadPhoto(named: photoNames[2])

let photos = await [firstPhoto, secondPhoto, thirdPhoto]
show(photos)

我想实现以下类似的功能。
let items = photoNames.map({ photo in
    async let item = downloadPhoto(named: photo)
    return item
}) 
let photos = await items
show(photos)

1
后端没有一次下载多张照片的API吗? - Sweeper
后端没有一个 - Pytan
3个回答

18

您可以使用任务组。请参见The Swift Programming Language: Concurrency中的Tasks and Task Groups部分(这似乎是您得到示例的地方)。

可以使用withTaskGroup(of:returning:body:)创建一个任务组来并行运行任务,然后在结束时汇总所有结果。

例如,以下是一个示例,它创建返回“name”和“image”的元组的子任务,并将组合的字典返回这些名称字符串与其相关图像值:

func downloadImages(names: [String]) async -> [String: UIImage] {
    await withTaskGroup(
        of: (String, UIImage).self,
        returning: [String: UIImage].self
    ) { [self] group in
        for name in names {
            group.addTask { await (name, downloadPhoto(named: name)) }
        }

        var images: [String: UIImage] = [:]

        for await result in group {
            images[result.0] = result.1
        }

        return images
    }
}

或者更简洁的说:

func downloadImages(names: [String]) async -> [String: UIImage] {
    await withTaskGroup(of: (String, UIImage).self) { [self] group in
        for name in names {
            group.addTask { await (name, downloadPhoto(named: name)) }
        }

        return await group.reduce(into: [:]) { $0[$1.0] = $1.1 }
    }
}

它们并行运行:

在此输入图片描述

但你可以从结果字典中提取它们:

let stooges = ["moe", "larry", "curly"]
let images = await downloadImages(names: stooges)

imageView1.image = images["moe"]
imageView2.image = images["larry"]
imageView3.image = images["curly"]

或者,如果您想按照原始顺序对数组进行排序,只需从字典中构建一个数组即可:

func downloadImages(names: [String]) async -> [UIImage] {
    await withTaskGroup(of: (String, UIImage).self) { [self] group in
        for name in names {
            group.addTask { await (name, downloadPhoto(named: name)) }
        }

        let dictionary = await group.reduce(into: [:]) { $0[$1.0] = $1.1 }
        return names.compactMap { dictionary[$0] }
    }
}

为什么要使用“returning”参数? - user652038
在这种情况下,返回类型可以被推断出来,因此不需要它。 - Rob

4

通道是最近的工具,截至本回答更新时它们提供了一种保持顺序的解决方案。

import AsyncAlgorithms

let photos = await Array(photoNames.mapWithTaskGroup(downloadPhoto))

import AsyncAlgorithms
import HeapModule

public extension Sequence where Element: Sendable {
  /// Transform a sequence asynchronously, and potentially in parallel.
  /// - Returns: An `AsyncSequence` which returns transformed elements, in their original order,
  /// as soon as they become available.
  func mapWithTaskGroup<Transformed: Sendable>(
    priority: TaskPriority? = nil,
    _ transform: @escaping @Sendable (Element) async -> Transformed
  ) -> AsyncChannel<Transformed> {
    let channel = AsyncChannel<Transformed>()
    Task { await mapWithTaskGroup(channel: channel, transform) }
    return channel
  }

  /// Transform a sequence asynchronously, and potentially in parallel.
  /// - Returns: An `AsyncSequence` which returns transformed elements, in their original order,
  /// as soon as they become available.
  func mapWithTaskGroup<Transformed: Sendable>(
    priority: TaskPriority? = nil,
    _ transform: @escaping @Sendable (Element) async throws -> Transformed
  ) -> AsyncThrowingChannel<Transformed, Error> {
    let channel = AsyncThrowingChannel<Transformed, Error>()
    Task {
      do {
        try await mapWithTaskGroup(channel: channel, transform)
      } catch {
        channel.fail(error)
      }
    }
    return channel
  }
}

// MARK: - private
private protocol AsyncChannelProtocol<Element> {
  associatedtype Element
  func send(_: Element) async
  func finish()
}

extension AsyncChannel: AsyncChannelProtocol { }
extension AsyncThrowingChannel: AsyncChannelProtocol { }

private extension Sequence where Element: Sendable {
  private func mapWithTaskGroup<Transformed: Sendable>(
    channel: some AsyncChannelProtocol<Transformed>,
    priority: TaskPriority? = nil,
    _ transform: @escaping @Sendable (Element) async throws -> Transformed
  ) async rethrows {
    typealias ChildTaskResult = Heap<Int>.ElementValuePair<Transformed>
    try await withThrowingTaskGroup(of: ChildTaskResult.self) { group in
      for (offset, element) in enumerated() {
        group.addTask(priority: priority) {
          .init(offset, try await transform(element))
        }
      }

      var heap = Heap<ChildTaskResult>()
      var lastSentOffset = -1
      for try await childTaskResult in group {
        heap.insert(childTaskResult)
        // Send as many in-order `Transformed`s as possible.
        while heap.min()?.element == lastSentOffset + 1 {
          await channel.send(heap.removeMin().value)
          lastSentOffset += 1
        }
      }

      channel.finish()
    }
  }
}

import HeapModule

public extension Heap {
  /// A "`Value`" that uses an accompanying `Heap.Element` for sorting  via a `Heap`.
  /// - Note: If `Value` itself is `Comparable`, it can of course be inserted into a Heap directly.
  ///   This type is explicitly for cases where a different sorting rule is desired.
  struct ElementValuePair<Value> {
    public var element: Element
    public var value: Value
  }
}

// MARK: - public
public extension Heap.ElementValuePair {
  init(_ element: Element, _ value: Value) {
    self.init(element: element, value: value)
  }
}

// MARK: - Comparable
extension Heap.ElementValuePair: Comparable {
  public static func < (lhs: Self, rhs: Self) -> Bool {
    lhs.element < rhs.element
  }

  /// Only necessary because Comparable: Equatable. 
  public static func == (lhs: Self, rhs: Self) -> Bool {
    fatalError()
  }
}

// MARK: - Sendable
extension Heap.ElementValuePair: Sendable where Element: Sendable, Value: Sendable { }

4
需要一些解释。仅有代码的答案并没有太大帮助。 - Sulthan
1
你的第二个扩展使用了一个内部类型。我认为这需要一些解释,因为它看起来像是容易出问题的东西。 - Sulthan

-3
如果结果的顺序不重要,可以使用 TaskGroup

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接