为什么只允许UI线程修改UI?

我知道如果我从不同的线程修改控件,我应该小心,因为WinForms和WPF不允许从其他线程修改控件的状态。

为什么这个限制到位了?

如果我可以编写线程安全的代码,我应该能够安全地修改控制状态。 那为什么会出现这种限制?

几个GUI框架具有此限制。 根据Java Concurrency in Practice一书中的原因,这是为了避免复杂的锁定。 问题是GUI控件可能必须对来自UI,数据绑定等的两个事件作出反应,这导致从几个不同的源锁定并因此导致死锁的风险。 为了避免这种情况,.NET WinForms(和其他UI)将对组件的访问限制为单个线程,从而避免锁定。

在窗口的情况下,当创建控件时,通过来自消息泵的消息执行UI更新。 程序员没有直接控制泵运行的螺纹,因此控制消息的到达可能导致控制状态的改变。 如果允许另一个线程(程序员直接控制)改变控件的状态,则必须设置某种同步逻辑以防止控制状态的损坏。 .Net中的控件不是线程安全的; 这是我怀疑的设计。 在设计,开发,测试和支持提供此function的代码方面,将同步逻辑放在所有控件中将是昂贵的。 程序员当然可以为控件提供自己代码的线程安全性,但不能用于与他的代码同时运行的.Net中的代码。 此问题的一个解决方案是将这些类型的操作限制为仅一个线程和一个线程,这使得.Net中的控制代码更易于维护。

.NET保留随时在您创建控件的线程中访问控件的权限。 因此来自另一个线程的访问永远不会是线程安全的。

您可能能够使自己的代码成为线程安全的,但是您无法将必要的同步原语注入到与代码中的代码匹配的内置WinForm和WPF代码中。 请记住,有很多消息在幕后传递,最终导致UI线程访问控件而你却没有意识到它。

控件线程亲和性的另一个有趣的方面是它可以 (虽然我怀疑他们永远不会)使用线程本地存储模式。 显然,如果您在其上创建的线程上访问控件,则无论您如何仔细地构建代码以防止multithreading代码的所有常见问题,都无法访问正确的TLS数据。

实际上,据我所知,从一开始就是这个计划! 每个控件都可以从任何线程访问! 并且仅仅因为当另一个线程需要访问控件时需要线程锁定 – 并且因为锁定很昂贵 – 所以制作了一个新的线程模型,称为“线程租用”。 在该模型中,相关控件将仅使用一个线程聚合到“上下文”中,从而减少所需的锁定量。 很酷,对吧?

遗憾的是,这种尝试过于大胆而无法成功(并且因为仍然需要锁定而有点复杂),所以良好的旧Windows窗体线程模型 – 使用单个UI线程和创建线程来声明控件的所有权 – 在wPF再次使用,让我们的生活更轻松?

Windows支持许多操作,特别是组合使用,本质上不是线程安全的。 应该发生什么,例如,当一个线程试图将一些文本插入以第50个字符开头的文本字段时,而另一个线程试图从该字段中删除前40个字符? Windows可能会使用锁来确保第二个操作在第一个操作完成之前无法启动,但是使用锁会增加每个操作的开销,如果一个实体上的操作需要,也会增加死锁的可能性操纵另一个人。 要求涉及特定窗口的操作必须在特定线程上发生,这比防止不安全的操作组合同时执行所需的要求更严格,但是它相对容易分析。 使用来自多个线程的控件并通过其他方式避免冲突通常会更加困难。