盲目使用InvokeRequired是不好的做法吗?

21

我是一名新手程序员,所以我可能完全错了,但这个问题困扰着我。

实际上,这是对这个问题的跟进。

被接受的答案是,你必须调用InvokeRequired来避免一些开销,因为有可能你已经在UI线程上操作。

从理论上讲,我同意这可以节省一些时间。经过一些测试,我发现使用Invoke大约要比正常调用一个操作花费两倍的时间(像n次设置标签的文本或在RichTextBox中放置一个非常大的字符串等测试)。

但是!然后就是实践。

MSDN文档说:

此属性可用于确定是否必须调用调用方法,如果您不知道哪个线程拥有控件,则此属性可能很有用。

在大多数情况下,当你尝试从另一个线程访问控件时,你知道它。实际上,我唯一能想到的情况是当控件从可以被线程X和所有者线程调用的方法访问时。而那对我来说是非常不太可能的情况。

而且,即使你真的不知道哪个线程试图操作控件,UI线程也不必那么频繁地更新。25-30 fps之间的任何东西都应该对你的GUI没问题。大多数在UI控件中进行的更改需要花费远远少于毫秒的时间来执行。

因此,如果我理解正确,唯一需要检查是否需要调用Invoke的情况是当你不知道哪个线程正在访问控件,并且GUI更新需要超过约40毫秒才能完成时。

接下来是我在http://programmers.stackexchange.com上提出的问题(链接)的答案。它表明当你不需要时,不应该忙于过早地进行优化。尤其是如果这会牺牲代码的可读性。

那么这就引出了我的问题:难道当你知道不同的线程访问控件并且只有当你知道你的UI线程可以访问代码的那部分并且你发现它应该运行得更快时,你才应该使用invoke吗?此外,你是否应该检查是否需要Invoke?

PS:在校对我的问题后,它听起来像是我在抱怨。但实际上我只是好奇为什么InvokeRequired似乎被许多比我经验丰富的程序员过度使用。


4
你的帖子做得很好。 - ChiefTwoPencils
3
我通常同意这种观点——如果你期望一个调用将在非UI线程上发生,那么使用InvokeRequired只会使代码更加混乱无序,没有实际意义。 - Matthew Watson
4
我的看法是:InvokeRequired 如此频繁地使用,是因为这些程序员曾经有过这样的伤痛经历,即使他们一开始确信不需要它。他们学会了避免在特定情况下思考是否需要它,而是总是将其放在代码中。这使他们更加高效(减少尝试确定是否需要它的时间,减少后来修复代码时花费的时间)。这很好。但同时也会让他们思考得更少,这是不好的。 - Treb
1
正如您所看到的,这是受到赞赏的。太多的帖子都不够好。看到额外的努力是很好的。 - ChiefTwoPencils
1
作为一名经常维护他人文档不完善代码的开发者,我讨厌多余的检查。在我的经验中,我开发的应用程序中显式调用UI方法的次数,在辅助线程和主UI线程中都是零。 - Martin James
显示剩余5条评论
2个回答

9
你在这里断章取义了。第一个问题 你链接的 链接另一个问题,该问题特别涉及编写一个线程安全的方法来访问 UI 控件。
如果你不需要对 UI 控件进行线程安全访问,因为你知道你不会从另一个线程更新它,那么当然,你不应该使用这种技术。只需更新你的 UI 控件,而不使用 InvokeRequiredInvoke
另一方面,如果调用将始终来自于 UI 线程之外的线程,则只需使用 Invoke 而无需首先检查 InvokeRequired
这导致了三个简单的规则:
  1. 如果你仅从 UI 线程更新控件,则既不使用 InvokeRequired 也不使用 Invoke
  2. 如果你仅从其他线程而非 UI 线程更新控件,则仅使用 Invoke
  3. 如果你从 UI 线程和其他线程都更新控件,则使用 InvokeInvokeRequired 结合使用。

我同意,但是它不应该是这样的吗:“3. 如果您从UI线程和其他线程更新控件,并且一切都按预期工作,请使用Invoke。4.如果您从UI线程和其他线程更新控件,仅使用Invoke太慢,请结合InvokeRequired使用Invoke。”? - Jordy
2
@Jordy:我不会这样做。不是因为过早优化,而是因为在调用线程可以是任何东西的情况下,InvokeRequired和Invoke的组合是一个固定的模式。 - Daniel Hilgarth

8
在实践中,人们倾向于从外部线程和拥有的线程调用相同的方法。通常模式是方法本身确定线程是否为拥有线程。如果是,则执行后续代码。如果不是,则该方法使用Invoke调用自身。
其一好处在于,它使代码更加紧凑,因为您只需要一个与操作相关的方法而非两个。
另一个可能更重要的好处在于,它减少了跨线程异常发生的机会。如果两个方法随时都可用,并且两个线程都可以选择其中之一,则似乎合法的方法调用可能引发异常。另一方面,如果只有一个适应情况的方法,它提供了更安全的接口

但是在我看到的大多数代码示例中,它被用作SetTextBox(string text)。但是像这样制作方法不是已经表明了显而易见的事实,即您正在尝试从UI线程外部访问它吗?在我看来,当您想要从ui线程更改TextBox时,您可能只需调用TextBox.Text。 - Jordy
@Jordy 这样做的重点在于,它允许您不必关心在调用方法时是否在 UI 线程上(即使在给定的调用点上,您可以确定)。它完全将检查留给方法本身。需要创建这样的方法只意味着您处于潜在的跨线程情况下,并不意味着您在任何给定时间都会从错误的线程调用。 - Theodoros Chatzigiannakis
我认为我开始明白你的观点了,但如果你想让你的代码线程安全,像锁定这样的东西似乎是更好的解决方案,不是吗? - Jordy
@Jordy 如果你说的是关于WPF控件本身如何被实现的,那么是的,对于使用它们的程序员来说,可能锁定会更方便。如果你说的是在假设控件已经是这样的情况下使你自己的代码线程安全,我不知道锁定如何有帮助,因为你不是在处理一个“无助”的资源,而是一个已经具有某种安全性的资源(这是一种你不能控制的安全性,所以理论上你不能使其与你的锁协作)。 - Theodoros Chatzigiannakis
我认为你在回答中提到的异常是由于代码不安全线程引起的异常。我只是发表了我想到的第一个解决线程不安全问题的方法。现在我明白你的意思了。 - Jordy
@Jordy 有点像。跨线程异常是因为WPF控件有某种写保护机制。 (无论这种写保护是否可以被称为“线程安全”在官方意义上的短语,我不确定 - 或许其他人能更好地解释这一点)。但由于存在这种保护,调用它的代码必须在调用之前检查其运行线程,否则调用代码就是线程不安全的。而我认为WPF控件留给你使调用代码安全的唯一空间就是Dispatcher。 - Theodoros Chatzigiannakis

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