为啥在主线程上调用东西是程序员的责任?

Posted

技术标签:

【中文标题】为啥在主线程上调用东西是程序员的责任?【英文标题】:Why is it the programmer’s responsibility to call things on the main thread?为什么在主线程上调用东西是程序员的责任? 【发布时间】:2020-02-10 21:43:29 【问题描述】:

为什么程序员有责任在主线程上调用 UI 相关的方法:

DispatchQueue.main.async 

理论上,这不能由编译器或其他代理来决定吗?

【问题讨论】:

理论上,如果 UIKit 的开发者无事可做。 我在回答中给出了正确的解释。检查并关注它! 【参考方案1】:

实际的答案是开发者的惯性和祖父。

Cocoa UI API 是巨大的——不,是巨大的。自 1990 年代以来,它也一直在不断发展。

在我年轻的时候,没有多核、64 位等任何东西,99.999% 的应用程序都在主线程上运行。时期。 (最初的 Mac OS,pre-OS X,甚至没有线程。)

后来,一些专门的任务可以在后台线程上运行,但大部分应用程序仍然在主线程上运行。

快进到今天,为后台执行分派数千个任务是微不足道的,CPU 可以运行 30 个或更多当前线程,很容易说“嘿,为什么编译器/API/OS 不处理这个主线程给我的东西?”但近乎不可能的是重新设计 Cocoa 代码和应用程​​序长达 4 年的时间来实现这一目标。

我要说的是,数以亿计的代码行都假定 UI 调用在主线程上同时执行。正如其他人指出的那样,没有任何切割器开关或预处理器可以撤销所有这些假设,修复所有这些潜在的死锁等。

(哎呀,如果编译器能够解决这种问题,我们甚至不必编写多线程代码;您只需让编译器对您的代码进行切片,以便它同时运行。)

最后,这样的改变是不值得的。我全职做 Cocoa 开发,我必须处理“来自后台线程问题的更新控制”的次数,最多每周一次左右。没有任何开发成本效益分析会花费一百万工时来解决已经有直接解决方案的问题。

现在,如果您从头开始开发一个新的、现代的 UI API,您只需让整个 UI 框架线程安全,整个问题就会消失。也许苹果公司在某个地方的实验室里有一个全新的、从头开始重新设计的 UI 框架,可以做到这一点。但这是我看到这种情况发生的唯一方式。

【讨论】:

感谢您对此的历史观点。我很欣赏通读这些内容,考虑到这种情况,这很有意义。也许你梦寐以求的这个全新框架就是 SwiftUI,但我不确定 SwiftUI 是否会这样做。【参考方案2】:

你会用一种挫败感代替另一种。

假设所有需要在主线程上调用的 UI 相关方法都是通过以下方式完成的:

使用DispatchQueue.main.async:您将隐藏异步行为,没有明显的方法可以“跟进”结果。这样的代码现在会失败:

label.text = "new value"
assert(label.text == "new value")

您会认为属性text 只是无害地分配了一些值。事实上,它将一个工作项排入队列以在主线程上异步执行。这样一来,您就打破了您的系统在完成该行时已达到其所需状态的期望。

使用DispatchQueue.main.sync:您将隐藏死锁的可能性。 main 队列上的同步代码可能非常危险,因为很容易无意中阻塞(在主线程上)自己等待这样的工作,从而导致死锁。

我认为实现此目的的一种方法是拥有一个专用于 UI 的隐藏线程。所有与 UI 相关的 API 都会切换到该线程来完成它们的工作。虽然我不知道这会有多昂贵(每次切换到该线程可能不会比等待锁快),但我可以想象有很多“有趣”的方式可以让你编写死锁代码。

【讨论】:

【参考方案3】:

只有在极少数情况下,UI 才会调用主线程中的任何内容,但出于安全考虑,用户登录超时除外。任何特定窗口的大多数与 UI 相关的方法都是在初始化窗口时启动的线程中调用的。 我宁愿管理我的 UI 调用而不是编译器,因为作为开发人员,我想要控制并且不想依赖第三方“黑匣子”。

【讨论】:

【参考方案4】:

查看https://developer.apple.com/documentation/code_diagnostics/main_thread_checker

仅从主线程更新用户界面!!!

【讨论】:

以上是关于为啥在主线程上调用东西是程序员的责任?的主要内容,如果未能解决你的问题,请参考以下文章

-[GCKCastDeviceProvider stopDiscovery] 必须在主线程上调用

为啥 NSManagedObjectContext 队列在主线程上执行?

为啥我的 AsyncTask 在主线程上运行?

在主线程和工作线程中加载动态库(内部调用COM dll)有什么区别?

为啥 .NET 应用程序会停止执行任何网络 I/O?

android编程为啥要更新界面?怎么更新?