Can I draw to the same CGContextRef from multiple threads?

依然范特西╮ 提交于 2020-01-23 02:12:12

问题


I'm making an app where I want to draw a lot of shapes - circles, boxes, lines, etc. Millions of them.

To test the performance of this, I threw together this simple UIView. Note that credit is due - I got inspired by this project.

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")
        }
    }
}

This works just fine. On my iOS simulator, it takes little over a minute to draw a million circles on top of each other.

I wanted to speed this up, so I tried drawing to the bitmap context from multiple threads.

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)

This did not work, however. I get an EXC_BAD_ACCESS when calling CGContextFillEllipseInRect(context, bounds).

Drawing to a CGContext in a background thread seems fine as long as it's the same thread that created it.

Anyone know of a way of getting this to work?


回答1:


1) You aren't actually waiting for the group you created to finish -- dispatch_group_wait is going to be called in that code before any of the blocks have been executed, so the enter / leave calls inside them won't have any effect. Use dispatch_group_async instead (see below).

2) You can't draw to a CGContext from two different threads at the same time -- you can see this if you add a println inside your drawing loop. It will work a few times, with varying results, but eventually you'll be end up with an error.

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)

Sample output:

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



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

The only solution to this is to jump back on a single thread for drawing, but that defeats what you were trying to do any way. If you have to do lots of calculations to decide what to draw, that could happen on separate threads, but drawing to CGContext itself isn't thread safe.



来源:https://stackoverflow.com/questions/27064119/can-i-draw-to-the-same-cgcontextref-from-multiple-threads

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!