我试图了解 Swift 中 async-await
的正确应用。假设一个async
方法,一种不进行外部调用的非IO方法,从后台线程调用来执行其进程,例如一些繁重的图像处理方法。
func processImage(image: UIImage) async -> UIImage {
///
}
Task {
let result = await processImage(image: image)
}
当代码暂停并等待结果时发生了什么?由于它不进行外部调用,因此该过程必须在线程池内的某个位置完成。由于它不是在调用该方法的线程中完成的,因此它必须在另一个线程上执行。是否创建子任务来执行流程?据我了解,
Task
是一个并发单位,单个任务不包含并发(async let
除外),所以这让我有点困惑。任务是并发还是非并发?据我所知,如果从主线程调用此方法,
async
方法的非阻塞方面会释放线程以供 UI 元素运行,从而提供无缝的视觉体验。但是,从后台线程调用异步方法有什么好处呢?我不是指能够返回结果或抛出错误的语法糖。如果该方法是从后台调用的非 IO 方法,那么与使用同步方法相比,非阻塞方面是否有任何好处?换句话说,它不阻塞什么?如果它是一个并行进程,它会利用更多的资源来有效地处理多个事物,但我不确定在这种情况下并发进程有什么好处。
最佳答案
如果您想使用async/await
,您需要停止考虑线程。在某种程度上,出于显而易见的原因,继续使用“在主线程上”和“在后台线程上”等短语是有用的,但这些几乎都是隐喻。
您只需要接受这一点,无论什么“线程”运行,await
都有神奇的力量说“保留我的位置”并允许计算机走开并执行某些操作直到我们等待的东西回到我们身边为止。没有任何东西阻挡,没有任何东西旋转。这确实是 async/await
要点的很大一部分。
(如果你想了解它在幕后是如何工作的,你需要找出什么是“延续”。但总的来说,这真的不值得担心;这只是让你的内部信仰体系井然有序的问题.)
然而,async/await
的总体优势是语法上的,而不是机械上的。也许您可以通过使用其他机制(Combine、DispatchQueue、Operation 等等)来完成实际上通过async/await
可以完成的所有操作。但经验表明,尤其是在 DispatchQueue 的情况下,初学者(和不太初学者)有 great difficulty reasoning关于异步代码时代码行的执行顺序。使用async/await
,这个问题就消失了:代码按照它出现的顺序执行,就好像它根本不是异步的一样。
不仅仅是程序员; 编译器无法推断 DispatchQueue 代码的正确性。它无助于让你犯错误(我确信你曾经犯过一些错误;我当然也犯过)。但async/await
不是那样的;恰恰相反:编译器可以推理您的代码,并可以帮助保持一切整洁、安全和正确。
对于您提出的实际示例,正确的实现是定义一个参与者,其工作是执行耗时的任务。根据定义,这不会是主要 Actor ;因为你定义了它,所以它就是我们所说的背景 Actor ;它的方法将自动从主线程中调用,并且由于编译器的出色,其他一切都将随之而来。
这是一个示例(来 self 的书),它正在做您所要求的那种事情 - 耗时的计算。这是一个 View ,当您调用其公共(public) drawThatPuppy
方法时,它会计算启动主线程的 Mandelbrot 的粗略图像,然后在其自身内描绘该图像。出于您的目的,需要注意的关键是在行中
self.bitmapContext = await self.calc.drawThatPuppy(center: center, bounds: bounds)
self.setNeedsDisplay()
短语self.bitmapContext =
和self.setNeedsDisplay
在主线程上执行,但对self.calc.drawThatPuppy
的调用在后台线程上执行,因为 calc 是一个参与者。然而,当 self.calc.drawThatPuppy 执行时,主线程并没有被阻塞;相反,其他主线程代码在此期间可以自由运行。这真是一个奇迹!
// Mandelbrot drawing code based on https://github.com/ddeville/Mandelbrot-set-on-iPhone
import UIKit
extension CGRect {
init(_ x:CGFloat, _ y:CGFloat, _ w:CGFloat, _ h:CGFloat) {
self.init(x:x, y:y, width:w, height:h)
}
}
/// View that displays mandelbrot set
class MyMandelbrotView : UIView {
var bitmapContext: CGContext!
var odd = false
// the actor declaration puts us on the background thread
private actor MyMandelbrotCalculator {
private let MANDELBROT_STEPS = 200
func drawThatPuppy(center:CGPoint, bounds:CGRect) -> CGContext {
let bitmap = self.makeBitmapContext(size: bounds.size)
self.draw(center: center, bounds: bounds, zoom: 1, context: bitmap)
return bitmap
}
private func makeBitmapContext(size:CGSize) -> CGContext {
var bitmapBytesPerRow = Int(size.width * 4)
bitmapBytesPerRow += (16 - (bitmapBytesPerRow % 16)) % 16
let colorSpace = CGColorSpaceCreateDeviceRGB()
let prem = CGImageAlphaInfo.premultipliedLast.rawValue
let context = CGContext(data: nil, width: Int(size.width), height: Int(size.height), bitsPerComponent: 8, bytesPerRow: bitmapBytesPerRow, space: colorSpace, bitmapInfo: prem)
return context!
}
private func draw(center:CGPoint, bounds:CGRect, zoom:CGFloat, context:CGContext) {
func isInMandelbrotSet(_ re:Float, _ im:Float) -> Bool {
var fl = true
var (x, y, nx, ny) : (Float, Float, Float, Float) = (0,0,0,0)
for _ in 0 ..< MANDELBROT_STEPS {
nx = x*x - y*y + re
ny = 2*x*y + im
if nx*nx + ny*ny > 4 {
fl = false
break
}
x = nx
y = ny
}
return fl
}
context.setAllowsAntialiasing(false)
context.setFillColor(red: 0, green: 0, blue: 0, alpha: 1)
var re : CGFloat
var im : CGFloat
let maxi = Int(bounds.size.width)
let maxj = Int(bounds.size.height)
for i in 0 ..< maxi {
for j in 0 ..< maxj {
re = (CGFloat(i) - 1.33 * center.x) / 160
im = (CGFloat(j) - 1.0 * center.y) / 160
re /= zoom
im /= zoom
if (isInMandelbrotSet(Float(re), Float(im))) {
context.fill (CGRect(CGFloat(i), CGFloat(j), 1.0, 1.0))
}
}
}
}
}
private let calc = MyMandelbrotCalculator()
// jumping-off point: draw the Mandelbrot set
func drawThatPuppy() async {
let bounds = self.bounds
let center = CGPoint(x: bounds.midX, y: bounds.midY)
self.bitmapContext =
await self.calc.drawThatPuppy(center: center, bounds: bounds)
self.setNeedsDisplay()
}
// turn pixels of self.bitmapContext into CGImage, draw into ourselves
override func draw(_ rect: CGRect) {
if self.bitmapContext != nil {
let context = UIGraphicsGetCurrentContext()!
context.setFillColor(self.odd ? UIColor.red.cgColor : UIColor.green.cgColor)
self.odd.toggle()
context.fill(self.bounds)
let im = self.bitmapContext.makeImage()
context.draw(im!, in: self.bounds)
}
}
}
关于swift - 在后台线程中使用 async-await 有什么好处?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/75354030/