我可以为多线程绘制使用同一个CGContextRef吗?

4

我正在制作一个应用程序,想要绘制很多形状 - 圆圈、方框、线条等等,数量达到数百万个。

为了测试其性能,我编写了这个简单的 UIView。请注意,这个项目是受到 这个项目 的启发而来。

import UIKit

let qkeyString = "label" as NSString
var QKEY = qkeyString.UTF8String
let qvalString = "com.hanssjunnesson.Draw" as NSString
var QVAL = qvalString.UTF8String

public class RenderImageView: UIView {

    var bitmapContext: CGContext?

    let drawQueue: dispatch_queue_attr_t = {
        let q = dispatch_queue_create(QVAL, nil)
        dispatch_queue_set_specific(q, QKEY, &QVAL, nil)

        return q
    }()

    public override init() {
        super.init()

        render()
    }

    required public init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)

        render()
    }

    override required public init(frame: CGRect) {
        super.init(frame: frame)

        render()
    }

    public override func drawRect(rect: CGRect) {
        if let bitmapContext = self.bitmapContext {
            let context = UIGraphicsGetCurrentContext()
            let image = CGBitmapContextCreateImage(bitmapContext)
            CGContextDrawImage(context, self.bounds, image)
        }
    }

    private func render() {
        dispatch_async(drawQueue) {
            let startDate = NSDate()

            let bounds = self.bounds
            UIGraphicsBeginImageContextWithOptions(bounds.size, false, 0.0)
            let context = UIGraphicsGetCurrentContext()
            self.bitmapContext = context

            CGContextSetFillColorWithColor(context, UIColor.whiteColor().CGColor)
            CGContextFillRect(context, bounds)

            CGContextSetFillColorWithColor(context, UIColor(red: 0.15, green: 0.4, blue: 0.8, alpha: 1.0).CGColor)

            for i in 1...1000000 {
                CGContextFillEllipseInRect(context, bounds)
            }

            UIGraphicsEndImageContext()

            self.setNeedsDisplay()

            let benchmark = startDate.timeIntervalSinceNow
            println("Rendering took: \(-benchmark*1000) Ms")
        }
    }
}

这个很好用。在我的iOS模拟器上,将一百万个圆形绘制在彼此之上只需要一分钟多一点。

我想要加快速度,所以我尝试从多个线程绘制位图上下文。

let group = dispatch_group_create()
for i in 1...100 {
    dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)) {
        dispatch_group_enter(group)

        CGContextFillEllipseInRect(context, bounds)

        dispatch_group_leave(group)
    }
}

dispatch_group_wait(group, DISPATCH_TIME_FOREVER)

然而,这并没有起作用。当调用CGContextFillEllipseInRect(context, bounds)时,我收到了一个EXC_BAD_ACCESS

只要是创建它的同一个线程,在后台线程中向CGContext绘图似乎是可以的。

有人知道如何让这个工作吗?


如果您不重用“context”引用,而是获取当前位图上下文,会发生什么? - Guy Kogus
获取位图上下文的唯一方法是在创建新图像上下文后调用UIGraphicsGetCurrentContext。否则,UIGraphicsGetCurrentContext将获取另一个上下文。在其中绘制不会显示在生成的图像中。 - Hans Sjunnesson
1个回答

4

1) 你实际上不需要等待你创建的组完成 -- 在任何块被执行之前,dispatch_group_wait 就会在那段代码中被调用,因此它们内部的进入 / 离开调用将没有任何效果。请改用 dispatch_group_async (见下文)。

2) 你不能同时从两个不同的线程绘制到一个 CGContext -- 如果你在绘制循环内部添加一个 println ,你就可以看到这一点了。它可能会工作几次,并产生不同的结果,但最终你会遇到错误。

let group = dispatch_group_create()
for i in 1...10 {
    dispatch_group_async(group, dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)) {
        for j in 1...100 {
            println("i:\(i), j:\(j)")
            CGContextFillEllipseInRect(context, bounds)
        }
    }
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER)

输出样例:

iiii::::4123,,,,    jjjj::::1111



ii:1, j:2
:5, j:1
i:6, j:1
EXC_BAD_ACCESS

唯一的解决方案是回到单线程绘图,但这样做会破坏你本来想要实现的目标。如果需要进行大量计算以确定要绘制的内容,则可以在单独的线程上执行,但将内容绘制到CGContext本身并不安全。


我猜使用多个核心的最佳方法是绘制“桶”,也就是将完整图像分成不同部分,然后将它们拼接在一起。 - Hans Sjunnesson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接