今年WWDC苹果公布了异步开发的新语法。这种语法使我们避免了之前写异步代码的一些问题,例如回调地狱,当回调代码有多层嵌套时,会使得我们的代码变得很难读懂。
接下来讲一下如何使用新语法写异步代码。
定义和调用异步方法
一个异步方法是一种特殊的方法,它区别于普通方法的是它可以在中途挂起。
声明异步方法的关键词是async,它放在方法参数声明的后面
当该方法可以抛出异常时,则将throws关键字放在async后面。
当我们调用异步方法时,则要在方法前面加上await关键字,代表代码执行到这里后被挂起等待恢复。
这段代码表示,当执行listPhotos和downloadPhotos这两个方法时,因为这两个方法都需要做网络请求,所以声明方法时都使用了async关键字,在调用它们时则使用await关键字来等待异步方法返回数据。
await关键字只能在特定可调用异步方法的地方使用:
1、 在用async关键字声明的方法中
2、在类,结构体,枚举里被@main修饰的方法中
3、在一个child task中
异步队列
当遍历一个异步队列时,则使用for-await-in方法来遍历:
这个循环方法表示,每一次循环都挂起等待得到当前元素。而循环的队列必须像普通队列遵循Squence协议一样,遵循AsyncSequence协议。
并行调用异步方法
当使用await来调用异步方法时,每一次只运行一段代码,接下来的代码都要等待async方法返回结果后再继续执行。例如:
这段方法表示每次都要等到上一张图片下载完成后才下载下一张图片,然而这是没有必要的,我们需要它们可以同时下载。这时引入async-let语法,如下:
这段代码中三个下载方法便是同时进行的,当最终要使用它们的返回值时,在使用await关键字修饰。在声明photos时使用await修饰,代表程序执行到这里后被挂起,等待有返回值后再执行后面的show(photos)。
Task和Task Groups
一个task是一个异步的工作单元,上面提到的async-let就是一个子task。我们也可以创建task group,然后添加tasks到这个group中,来创建动态数量的tasks。
这段代码就是遍历photoNames数组来创建多个子task来下载图片。
Unstructured Concurrency
以上讲述的代码都是structured Concurrency。而除此之外,还有Unstructured Concurrency。我们使用Task.init(priority:operation:)来声明一个在当前actor上运行的unstructured task。用Task.detached(priority:operation:)来声明不在当前actor上运行的unstructured task。actor个人理解这里的意思是线程。这两个方法都返回一个task handle来使用户可以对task进行操作,比如await或者cancel。
Task Cancellation
当task被取消时,一般可能出现下面的操作:
1、抛出错误信息,比如CancellationError
2、返回nil或者空的集合
3、返回部分完成的工作
我们可以调用Task.checkCancellation(),来检查task是否被取消并抛出错误。或者调用Task.isCancelled来自己处理task取消后的操作。
手动调用取消task: Task.cancel().
Actors
actors跟classes一样都是reference type。 Classes Are Reference Types 提到了reference type的一些概念。但是与classes有区别的是,actors一次只允许一个task来访问它的变量,这使得它在多线程交互中变得安全。
上面代码定义了一个actor对象,并声明了它,而在访问对象属性时就要使用await修饰,因为这个对象有可能正在被别的线程访问,所以需要等待别的线程访问结束后,我们才能进行访问。
在actor中访问它自身的变量时,则不需要使用await修饰。
当你尝试获取actor的属性而不使用await修饰时,你会得到一个编译错误
Swift保证只有在actor内部访问它自身的变量时不需要使用await,而在外部则需要,这被称为actor isolation。
本文参考翻译自:https://docs.swift.org/swift-book/LanguageGuide/Concurrency.html