上一节,虽然我们实现了预期的功能,不过你可能会实现方式感到有些不满。毕竟我们的目标是尽可能避免回调函数嵌套,结果居然在 task group 上又走回了嵌套的路数,这不禁让我们有种终日打雁,却被燕啄了眼的感觉。

怎么能优化一下呢?这里,分享一种基于 enum 的方法。之所以上一节采取了嵌套 task group 的方式,本质原因就是处理食材和预热烤箱的方法返回值类型不同。我们只要把这种不同,用 enum 的两个 case 区分开就好了。

enum Preparation {
  case ingredient(Food)
  case device
}

其中,ingredient 表示食材的准备,device 表示对设备的准备,由于我们只有烤箱,这里就不定义关联值了。

接下来,我们要让处理食材和预热烤箱的方法,都返回 Preparation

func chopVegetable() async -> Preparation {
  print("Chopping vegetables")
  return .ingredient(.vegetable)
}

func marinateMeat() async -> Preparation {
  print("Marinate meat")
  return .ingredient(.meat)
}

func preheatOven() async -> Preparation {
  print("Preheat oven.")
  return .device
}

现在,我们就可以把这三个任务,放在同一个 task group 里了:

func makeDinnerWithTaskGroup() async -> Meal {
  var foods: [Food] = []
  let oven = Oven()

  try await withTaskGroup(of: Preparation.self) { 
    group in
    let dinner = Dinner()
    
    group.async {
      await dinner.chopVegetable()
    }
    
    group.async {
      await dinner.marinateMeat()
    }
    
    group.async {
      await oven.preheatOven()
    }
    
    while let prep = await group.next(),
      case Preparation.ingredient(let food) = prep {
      foods.append(food)
    }
  }

  return await oven.cook(foods, seconds: 300)
}

这样调整过后,在处理任务结果的时候,我们用 while 替代了 for,在每一次循环的时候用 group.next() 方法得到每个任务返回的 Preparation 对象,再从中提取出 Food 的时候,就把它加入 foods 数组。

What's next?

这样,我们的改造就完成了。至于这样的方式是好是坏,暂时还很难下结论。毕竟,简化异步结构的代码是以修改若干 API 返回值为代价的,这可能只在项目早起规划的时候更为可行。另外,Preparation 也为其它代码带来了额外的间接性。大家知道有这样一种套路,结合自己实际情况使用就好了。

说到这,关于什么是结构化并发模型,大家应该有一个较为具体的了解了。它最大的好处,就是让我们可以用并发任务的逻辑关系来编写异步代码。相比之前基于线程同步原语的编程方式,它明显更易于理解和调试。

接下来,我们扩展一下这个例子,来看看 task group 中如果有任务发生了错误怎么办,以及如何在 task group 中取消任务。

所有订阅均支持 12 期免息分期

¥ 59

按月订阅

一个月,观看并下载所有视频内容。初来泊学,这可能是个最好的开始。

开始订阅

¥ 512

按年订阅

一年的时间,让我们一起疯狂地狩猎知识吧。比按月订阅优惠 28%

开始订阅

¥ 1280

泊学终身会员

永久观看和下载所有泊学网站视频,并赠送 100 元商店优惠券。

我要加入
如需帮助,欢迎通过以下方式联系我们