来自 HTML 主线程上的 NSAttributedString 的行为就像多线程一样
NSAttributedString from HTML on main thread behaves as if multithreading
我正在将一些 HTML 转换为主线程上的 NSAttributedString
(Apple 告诉您的方式)。它需要一些时间,然后继续执行块的其余部分。
现在,如果另一个块也在 main 线程中排队到 运行(例如,在从 HTTP 请求获得响应之后),我希望它运行 after 其他一切都完成了,但事实并非如此:它们 运行 并行,就好像它们在不同的线程上一样。我确实在各处都放置了断言,以确保它在主线程上。
我做了一个实验 "Single View App" 项目来测试这个,一个文件包含一个很长的 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("")
}
}
如果你 运行 它,这就是控制台的样子:
...全部混合在一起,这是令人惊讶的(对我来说)行为。
但是,如果在 viewDidLoad()
中您注释掉对 parseHTML()
的调用并取消注释 slowOperation()
,您将得到如下内容:
...这是我所期望的。那么,这里发生了什么?我对线程如何工作的理解是错误的吗?
我最初的猜测是正确的。 NSAttributedString init(data:options:documentAttributes:)
的实现调用了 CFRunLoopRun()
。这样做允许队列(在本例中为主队列)中的其他排队 blocks/closures 到 运行.
这就是为什么您在主队列上看到似乎是异步输出的原因。
我将您的代码放入一个简单的命令行应用程序中,并在 dispatchStuff
中的 print
上设置了一个断点。堆栈跟踪显示在调用 NSAttributedString init
期间有一个对 _CGRunLoopRun
的内部调用,这导致从 dispatchStuff
.
调用排队的闭包之一
我正在将一些 HTML 转换为主线程上的 NSAttributedString
(Apple 告诉您的方式)。它需要一些时间,然后继续执行块的其余部分。
现在,如果另一个块也在 main 线程中排队到 运行(例如,在从 HTTP 请求获得响应之后),我希望它运行 after 其他一切都完成了,但事实并非如此:它们 运行 并行,就好像它们在不同的线程上一样。我确实在各处都放置了断言,以确保它在主线程上。
我做了一个实验 "Single View App" 项目来测试这个,一个文件包含一个很长的 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("")
}
}
如果你 运行 它,这就是控制台的样子:
...全部混合在一起,这是令人惊讶的(对我来说)行为。
但是,如果在 viewDidLoad()
中您注释掉对 parseHTML()
的调用并取消注释 slowOperation()
,您将得到如下内容:
...这是我所期望的。那么,这里发生了什么?我对线程如何工作的理解是错误的吗?
我最初的猜测是正确的。 NSAttributedString init(data:options:documentAttributes:)
的实现调用了 CFRunLoopRun()
。这样做允许队列(在本例中为主队列)中的其他排队 blocks/closures 到 运行.
这就是为什么您在主队列上看到似乎是异步输出的原因。
我将您的代码放入一个简单的命令行应用程序中,并在 dispatchStuff
中的 print
上设置了一个断点。堆栈跟踪显示在调用 NSAttributedString init
期间有一个对 _CGRunLoopRun
的内部调用,这导致从 dispatchStuff
.