2025-02-08 15:57:01
,某些文章具有时效性,若有错误或已失效,请在下方留言。Each task in a Swift task group must return the same type of data as all the other tasks in the group, which is often problematic – what if you need one task group to handle several different types of data?
Swift 任务组中的每个任务都必须返回与该组中所有其他任务相同类型的数据,这通常是有问题的 – 如果您需要一个任务组来处理多种不同类型的数据,该怎么办?
In this situation you should consider using async let
for your concurrency if you can, because every async let
expression can return its own unique data type. So, the first might result in an array of strings, the second in an integer, and so on, and once you’ve awaited them all you can use them however you please.
在这种情况下,如果可以,您应该考虑对并发使用 async let
,因为每个 async let
表达式都可以返回其自己的唯一数据类型。因此,第一个可能会产生一个字符串数组,第二个可能产生一个整数,依此类推,一旦你等待了它们,你就可以随心所欲地使用它们。
However, if you need to use task groups – for example if you need to create your tasks in a loop – then there is a solution: create an enum with associated values that wrap the underlying data you want to return.
但是,如果您需要使用任务组(例如,如果您需要在循环中创建任务),那么有一个解决方案:创建一个具有关联值的枚举,用于包装要返回的基础数据。
Using this approach, each of the tasks in your group still return a single data type – one of the cases from your enum – but inside those cases you can place the unique data types you’re actually using.
使用这种方法,组中的每个任务仍然返回单个数据类型(枚举中的一种情况),但在这些情况下,您可以放置您实际使用的唯一数据类型。
This is best demonstrated with some example code, but because it’s quite a lot I’m going to add inline comments so you can see what’s going on:
这最好通过一些示例代码来演示,但是由于代码相当多,我将添加内联注释,以便您了解发生了什么:
// A struct we can decode from JSON, storing one message from a contact.
struct Message: Decodable {
let id: Int
let from: String
let message: String
}
// A user, containing their name, favorites list, and messages array.
struct User {
let username: String
let favorites: Set<Int>
let messages: [Message]
}
// A single enum we'll be using for our tasks, each containing a different associated value.
enum FetchResult {
case username(String)
case favorites(Set<Int>)
case messages([Message])
}
func loadUser() async {
// Each of our tasks will return one FetchResult, and the whole group will send back a User.
let user = await withThrowingTaskGroup(of: FetchResult.self) { group in
// Fetch our username string
group.addTask {
let url = URL(string: "https://hws.dev/username.json")!
let (data, _) = try await URLSession.shared.data(from: url)
let result = String(decoding: data, as: UTF8.self)
// Send back FetchResult.username, placing the string inside.
return .username(result)
}
// Fetch our favorites set
group.addTask {
let url = URL(string: "https://hws.dev/user-favorites.json")!
let (data, _) = try await URLSession.shared.data(from: url)
let result = try JSONDecoder().decode(Set<Int>.self, from: data)
// Send back FetchResult.favorites, placing the set inside.
return .favorites(result)
}
// Fetch our messages array
group.addTask {
let url = URL(string: "https://hws.dev/user-messages.json")!
let (data, _) = try await URLSession.shared.data(from: url)
let result = try JSONDecoder().decode([Message].self, from: data)
// Send back FetchResult.messages, placing the message array inside
return .messages(result)
}
// At this point we've started all our tasks,
// so now we need to stitch them together into
// a single User instance. First, we set
// up some default values:
var username = "Anonymous"
var favorites = Set<Int>()
var messages = [Message]()
// Now we read out each value, figure out
// which case it represents, and copy its
// associated value into the right variable.
do {
for try await value in group {
switch value {
case .username(let value):
username = value
case .favorites(let value):
favorites = value
case .messages(let value):
messages = value
}
}
} catch {
// If any of the fetches went wrong, we might
// at least have partial data we can send back.
print("Fetch at least partially failed; sending back what we have so far. \(error.localizedDescription)")
}
// Send back our user, either filled with
// default values or using the data we
// fetched from the server.
return User(username: username, favorites: favorites, messages: messages)
}
// Now do something with the finished user data.
print("User \(user.username) has \(user.messages.count) messages and \(user.favorites.count) favorites.")
}
await loadUser()
I know it’s a lot of code, but really it boils down to two things:
我知道这需要大量的代码,但实际上可以归结为两件事:
- Creating an enum with one case for each type of data you’re expecting, with each case having an associated value of that type.
为您期望的每种数据类型创建一个具有一个 case 的枚举,每个 case 都有一个该类型的关联值。 - Reading the results from your group’s tasks using a
switch
block that reads each case from your enum, extracts the associated value inside, and acts on it appropriately.
使用switch
块读取组任务的结果,该块从枚举中读取每个 case,提取其中的关联值,并对其进行适当的作。
So, it’s not impossible to handle heterogeneous results in a task group, it just requires a little extra thinking.
因此,在任务组中处理异构结果并非不可能,只是需要一点额外的思考。