【问题标题】:Can I draw to the same CGContextRef from multiple threads?我可以从多个线程绘制到同一个 CGContextRef 吗?
【发布时间】:2014-11-21 14:54:49
【问题描述】:

我正在制作一个应用程序,我想在其中绘制很多形状 - 圆形、方框、线条等。 数以百万计。

为了测试它的性能,我把这个简单的 UIView 放在一起。请注意,信用到期 - 我受到 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")
        }
    }
}

这很好用。在我的 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 引用,而是获取当前位图上下文会怎样?
  • 获取位图上下文的唯一方法是在创建新的图像上下文后调用 UIGraphicsGetCurrentContext。否则, UIGraphicsGetCurrentContext 将获得另一个上下文。绘图不会显示在结果图像中。

标签: ios objective-c cocoa swift core-graphics


【解决方案1】:

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 本身并不是线程安全的。

【讨论】:

  • 我想使用多核的最佳方式是绘制“桶”,即,将完整图像的各个部分分开,然后将它们拼接在一起。
猜你喜欢
  • 1970-01-01
  • 2012-07-14
  • 1970-01-01
  • 2021-03-04
  • 2015-02-23
  • 2013-04-07
  • 2023-03-27
  • 1970-01-01
  • 2014-11-14
相关资源
最近更新 更多