如果该线程不修改UI元素,是否可以从另一个线程访问UI元素?

3
假设运行在实例化表单/控件/元素的线程中的代码(通常是主线程)不会同时修改/访问该元素,是否可以:
  1. 获取TextBox的Text属性。

  2. 枚举ListView。

  3. 订阅Form的Closing事件。(知道钩子将从实例化该窗体的线程中调用)

我已经尝试了所有3个,并且程序似乎没有抱怨。我一直认为您必须调用任何想要甚至远程触摸任何UI相关内容(读取或写入)的调用。
我非常清楚地了解为什么需要使用IsInvokeRequired / Invoke模式来修改元素,但我无法看到访问属性/事件会引起任何问题。
3个回答

5
肯定是可以的,但是可能会导致意外的行为。此外,还需要考虑其他与线程相关的错误,例如竞争条件/死锁,请参见托管线程最佳实践
为了安全起见,我建议始终在UI线程上访问UI。

所以,基本上,即使我遵循了所有线程最佳实践,仍然可能会发生一些未记录的奇怪情况? - Gabriel G. Roy
1
如果您在UI线程上不访问UI组件,那么您就没有遵循最佳实践。如果您开始处理跨线程访问,它肯定会在某个时候爆炸,但不一定是立即发生。 - James
2
@Deli 说到底,最重要的是只有一个线程在任何时候访问一个“控件”。实施这一点最简单的方法是只允许一个线程访问任何控件,因为这样你可以保证没有其他线程同时修改控件。如果您可以确保没有其他线程正在修改控件,则可以从非UI线程访问它,但由于GUI应用程序涉及的复杂性,这并不推荐。 - Servy

3

您需要做些什么来确保在您读取控件时UI线程不会修改它?整个将调度委托到UI线程的原因是您不需要担心这种情况。特别是枚举列表框将是最容易出错的,因为它需要最长的时间(从而创建了最大的竞争条件窗口)。您应该将这三个事情都调度到UI线程。


2

这是不安全的。UI线程可能(并且很可能会)在您从另一个线程读取时更改控件的状态。您的读取可能会捕获控件处于半成品状态。现在它可能运行良好,但迟早会失败......可能是不可预测和引人注目的。

不,您可能不需要使用Invoke模式。坦白地说......该模式通常是最糟糕的选择。通常最好让工作线程进行重活,并通过队列向UI线程发送新数据或进度信息,然后让UI线程通过定时器获取这些信息。这有几个优点。

  • 消除了昂贵的InvokeBeginInvoke操作。
  • UI线程决定何时以及如何经常使用新数据更新自己,而不是由工作线程来决定。
  • 您可以获得更多的工作线程吞吐量,因为它不必等待Invoke返回。
  • BeginInvoke相比,没有超过UI消息队列的风险。
  • 解耦UI和工作线程交互。

您需要枚举ListView并构建一个可以安全访问的单独的数据结构。如果ListView包含大量项目,请考虑同时维护一个单独的集合与控件。这样,您就可以在更长时间内分散数据复制的负担,在那段时间内可能不会被注意到。毕竟,我们不希望复制操作冻结UI线程,否则用户将会注意到。像ConcurrentBag这样的数据类型可能是一个好选择,因为它可以在工作线程正在读取它时由UI线程安全地修改。


InvokeBeginInvoke并不是特别昂贵的...实际上拥有一个独立的专用定时器可能会更加耗费资源。对于您的第二个观点,如果更新会很多,可以缓冲这些更新。对于第三个观点,如果在该上下文中存在问题,可以使用非阻塞版本;对于第四个观点,同样可以通过缓冲输出来解决;第五个观点是一个特别好的观点。我更喜欢使用其他方法来解决这个问题而不是使用定时器。对于较新的版本,您可以使用IProgress接口。 - Servy
@Servy:你提出的所有观点都很好,也都被我接受了。有很多事情可以做来减轻我的观点。但最终......当我走ISynchronizeInvoke这条路时,代码总是会变得臭味相投。 - Brian Gideon

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