为什么从另一个线程访问GUI元素是错误的?

4

在我使用过的所有GUI库(Swing、Android、Windows Forms、WPF)中,都有一个黄金规则,即不能从另一个线程(非GUI线程)访问或修改GUI元素。我想这个规则适用于任何GUI库。违反此规则很可能导致应用程序崩溃。然而,最近我一直在思考,为什么会这样?我找不到任何深入的解释。那么,这个规则的低级解释是什么?

4个回答

3

除非明确设计和构建为线程安全,否则没有任何软件是线程安全的。

图形用户界面(GUI)是一个复杂的有状态的系统,使其线程安全将是“代价高昂”的。


1
通常情况下,这是有一个非常简单的原因。UI函数通常不是线程安全的(使它们线程安全会降低性能)。

0
据我所知,这简直是不正确的:只要正确应用线程安全技术,Java中的每个对象都可以被并发访问。事实上,Java Swing对象大多数情况下都没有准备好进行多线程操作,因此您需要执行外部同步。
在GUI中,有几种情况需要多个线程进行协作:游戏、视觉效果、用户事件等。
有关GUI和多线程的更多信息: https://docs.oracle.com/javase/tutorial/uiswing/concurrency/dispatch.html

0
在你列出的那些中,有些可能是现有机制的包装器,因此您必须通过底层GUI框架间接回答问题。对于诸如Qt之类的多平台GUI框架,您还将拥有决定可能性和不可能性的最低公共分母。
现在,为什么访问GUI不是线程安全的?在我最熟悉的情况下(win32和X11),访问通常是通过发送请求并有时等待相应的答案来间接执行的。这通常以原子方式工作,即使跨进程边界,因此这不是直接导致问题的原因。但是,如果您从多个线程执行此操作,则最坏的情况是数据以不协调的方式进行修改。例如,如果您从两个线程读取,修改和写入同一个小部件,则这些操作可能会交错进行,因此只有一个线程的修改实际上会被应用。
不支持跨线程访问的其他原因:
在win32中,消息队列是线程本地的,这意味着只有创建窗口的线程才能找到并处理该窗口的消息。我想这是一个遗留问题,因为早期的进程只有单线程,消息队列只是全局的。将其设为线程本地与使errno线程安全的方法相同。
另一个原因是,在表示某些GUI元素的进程内创建支持对象。例如,MFC(在win32上)使用从操作系统部件句柄到表示该对象的C++对象的映射。该映射存储在线程本地存储中(遵循线程本地消息队列),对C++对象的访问不受互斥锁保护。从不同的线程访问这些对象是不好的,不是因为它们代表GUI对象,而是因为它们没有同步,就这么简单。
如果您考虑修改小部件树的结构(例如浏览器中的DOM树),则要么详细了解应用程序的其他部分正在做什么,要么需要在每次操作之前锁定对整个树的访问,以确保安全。不用说,这实际上防止了任何并行操作,因此您还可以采取下一步,要求所有操作都来自一个线程,从而节省整个多线程开销。

话虽如此,我相信Qt和C#(以及可能还有其他语言)实际上支持一些跨线程操作。它们会执行一些(更或者更少)晦涩的魔法,将调用转发到GUI线程,并将结果再次转发回调用线程。换句话说,它们试图使必要的线程间通信对程序员更加方便,同时保留单线程GUI的效率和简洁性。这不仅限于GUI处理,而是一种通用方法,只是对GUI特别重要。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接