swift - 在后台线程中使用 async-await 有什么好处?

标签 swift multithreading asynchronous async-await concurrency

我试图了解 Swift 中 async-await 的正确应用。假设一个async方法,一种不进行外部调用的非IO方法,从后台线程调用来执行其进程,例如一些繁重的图像处理方法。

func processImage(image: UIImage) async -> UIImage {
    ///
}

Task {
   let result = await processImage(image: image)
}
  1. 当代码暂停并等待结果时发生了什么?由于它不进行外部调用,因此该过程必须在线程池内的某个位置完成。由于它不是在调用该方法的线程中完成的,因此它必须在另一个线程上执行。是否创建子任务来执行流程?据我了解,Task是一个并发单位,单个任务不包含并发(async let除外),所以这让我有点困惑。任务是并发还是非并发?

  2. 据我所知,如果从主线程调用此方法,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/

相关文章:

c++ - 为什么 4 个进程比 4 个线程好?

postgresql - 在对象数组上执行异步 pg-node 查询

ios - CoreData 阻塞 UI

ios - 初始化时将数据传递给 UITableViewCell

java - 在线程运行时使用 OSGi 控制台

java - 这个多线程 Java 代码是如何工作的?

c# - 在事件处理程序上使用 lambda exp 时如何等待多个任务

javascript - 如何在 Grunt 任务中保持服务器监听运行?

ios - 如何调用另一个类中定义的函数?

Swift:从两个变量名中引用特定变量