主线程上来自 HTML 的 NSAttributedString 表现得好像多线程

mal*_*lef 4 html concurrency multithreading nsattributedstring swift

我正在将一些 HTML 转换NSAttributedString为主线程上的an (Apple 告诉您的方式)。这需要一些时间,然后它会继续执行块的其余部分。

现在,如果另一个块也排队在线程中运行(例如在从 HTTP 请求获得响应之后),我希望它其他所有事情都完成,但事实并非如此:它们并行运行,好像他们在不同的线程上。我确实在各处放置了断言,以确保它在主线程上。

我做了一个实验“单视图应用程序”项目来测试这个,文件包含一个很长的 html 字符串<p>lorem</p> ipsum <b>dolor</b> <i><u>sit</u> amet</i>和一个带有以下代码的视图控制器:

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        dispatchStuff()
        for _ in 0..<10 {
            // slowOperation()
            parseHTML()
        }
    }

    func dispatchStuff() {
        for i in 0..<10 {
            let wait = Double(i) * 0.2
            DispatchQueue.main.asyncAfter(deadline: .now() + wait) {
                assert(Thread.isMainThread, "not main thread!")
                print(" dispatched after \(wait) seconds")
            }
        }
    }

    // just loads a big lorem ipsum full of html tags
    let html: String = {
        let filepath = Bundle.main.path(forResource: "test", ofType: "txt")!
        return try! String(contentsOfFile: filepath)
    }()

    var n = 0
    func slowOperation() {
        n += 1
        assert(Thread.isMainThread, "not main thread!")
        print("slowOperation \(n) START")
        var x = [0]
        for i in 0..<10000 {
            x.removeAll()
            for j in 0..<i {
                x.append(j)
            }
        }
        print("slowOperation \(n) END")
        print("")
    }

    var m = 0
    func parseHTML() {
        m += 1
        assert(Thread.isMainThread, "not main thread!")
        print("parseHTML \(m) START")
        let options = [NSAttributedString.DocumentReadingOptionKey.documentType: NSAttributedString.DocumentType.html]
        let attrString = try! NSAttributedString(data: Data(html.utf8), options: options, documentAttributes: nil)
        print("parseHTML \(m) END")
        print("")
    }
}

Run Code Online (Sandbox Code Playgroud)

如果你运行它,控制台是这样的:

parseHTML() 未注释

...混合在一起,这就是(对我而言)令人惊讶的行为。

但是,如果viewDidLoad()您在注释中对调用parseHTML()和取消注释进行注释slowOperation(),您将得到如下内容:

slowOperation() 未注释

......这是我所期望的。那么,这里发生了什么?我对线程如何工作的理解是错误的吗?

rma*_*ddy 7

我最初的怀疑是正确的。的实现NSAttributedString init(data:options:documentAttributes:)使调用CFRunLoopRun(). 这样做允许队列上的其他排队块/闭包(在这种情况下是主队列)运行。

这就是为什么您会在主队列上看到似乎是异步输出的原因。

我将您的代码放入一个简单的命令行应用程序中,并在printin上设置了一个断点dispatchStuff。堆栈跟踪显示,在调用 期间,NSAttributedString init有一个内部调用会_CGRunLoopRun导致调用来自 的排队闭包之一dispatchStuff

在此处输入图片说明

  • `NSAttributedString init(data:options:documentAttributes:)` 的文档指出:*“不应从后台线程调用 HTML 导入器(即,选项字典包含值为 html 的 documentType)。它会尝试与主线程同步,失败和超时。从主线程调用它是有效的(但如果 HTML 包含对外部资源的引用,仍然会超时,应该不惜一切代价避免这种情况)。”*。 (2认同)