通过前面的例子可以看到,task group 用来表达具有依赖关系的异步任务非常方便。但无论是 task group 对作用域的严格控制,还是在其中创建任务的方法,如果用它来表达所有的异步任务,不免有些过于繁琐。一个最明显的例子就是,我们无法在同步函数中创建 task group,就更别谈什么执行异步任务了。因此,有时候你会觉得,还是简简单单用个回调函数更好。

为了解决这个问题,Swift 结构化并发模型中还定义了另外一种叫 unstructured task 的任务。还是基于之前的例子,我们直接通过代码来理解它的用法。

首先,给 Meal 添加一个 eat 方法:

struct Meal {
  func eat() {
    print("Yum!")
  }
}

其次,再定义一个吃晚餐的全局函数 eat,它接受一个可以制作出晚餐的任务作为参数:

func eat(_ task: Task<Meal, Error>) async throws {
  let meal = try await task.value
  meal.eat()
}

这里,Task 就是刚才提到的 unstructured task,它的两个泛形参数分别表示这个任务正常完成以及出错时的返回值。在 eat 的实现里,我们可以通过 Taskvalue 属性得到这个任务完成后的返回值,也就是 Meal 对象。之后,就可以调用 meal.eat 开吃了。

实际上,Task 对象可以理解为是用来探查任务自身的一个句柄,除了返回值之外,我们还可以用它查询任务的取消状态以及取消任务。

在异步环境中使用 Task

那么,该如何创建一个返回 Meal 的任务并使用它呢?先来看在异步环境中的情况:

@main
struct MyApp {
  static func main() async {
    let meal = Task(priority: .userInitiated) {
      () -> Meal in
      try await makeDinnerWithThrowingTaskGroup()
    }
    
    try? await eat(meal)
  }
}

Task 有两个版本的 init 方法:

public init(
  priority: TaskPriority? = nil, 
  operation: @escaping @Sendable () async -> Success)

public init(
  priority: TaskPriority? = nil, 
  operation: @escaping @Sendable () async throws -> Success)

其中:

  • 第一个参数是 TaskPriority 类型的对象,表示任务的优先级;
  • 第二个参数是个异步 closure,表示这个任务要执行的代码,这两个 init 方法的区别,主要就是 closure 是否会抛出异常。这个 closure 没有参数,但允许让我们指定一个返回值,表示任务成功执行后的结果。在我们的例子中,当然就是 Meal 了。至于那个 @Sendable 修饰,我们暂时先忽略它就好了;

在这个 closure 的实现里,我们直接调用了上一节定义的 makeDinnerWithThrowingTaskGroup() 函数。这样,一个可以创造出晚餐的任务就定义好了。Task 对象一旦创建完,任务就开始执行了。之后,当我们把它传递给全局函数 eat,此时的 meal 就相当于这个任务的句柄,在 eat 的实现里,就可以等待 meal.value 来获取任务的返回值。然后,我们就能在控制台看到这样的结果了:

Chopping vegetables
Marinate meat
Preheat oven.
Cook 300 seconds.
Yum!

在同步环境中执行任务

接下来,回到这一节开始的问题,如何在同步环境中执行异步函数 eat 呢?我们注意到,Task.init 并不是异步的,因此,把异步函数用 Task 包一下就好了。像这样:

@main
struct MyApp {
  static func main() {
    Task(priority: .userInitiated) {
      let meal = Task { () -> Meal in
        try await makeDinnerWithThrowingTaskGroup()
      }

      try await eat(meal)
      exit(EXIT_SUCCESS)
    }

    dispatchMain()
  }
}

当然,执行的结果是一样的,我们就不重复了。

Unstructured task 的嵌套

这一节最后,我们再回过头看看刚才写的同步 main 函数。我们使用了一个嵌套的 Task 结构,外面的叫做父任务,里面的叫做子任务。

当在一个 Task 中创建子任务的时候,子任务会继承父任务的优先级,因此,即便我们没有指定内层任务的优先级,它的优先级也是 .userInitiated。我们可以把 main 改成这样来观察:

@main
struct MyApp {
  static func main() {
    Task(priority: .userInitiated) {
      let meal = Task { () -> Meal in
        print(Task.currentPriority == .userInitiated) // True
        return try await makeDinnerWithThrowingTaskGroup()
      }
      
      try await eat(meal)
      exit(EXIT_SUCCESS)
    }

    dispatchMain()
  }
}

这里,Task.currentPriority 用来获取当前正在执行的任务的优先级。我们应该可以在控制台看到 True

What's next?

至此,关于 unstructured task 的基础知识,我们就说的差不多了。实际上,除了这种较为独立的任务之外,Swift 结构化并发模型中还提供了一种任务,叫做 detached task。下一节,我们来聊聊它,并补充一些关于独立任务还没提到的内容。

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

¥ 59

按月订阅

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

开始订阅

¥ 512

按年订阅

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

开始订阅

¥ 1280

泊学终身会员

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

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