自动化 InvokeRequired 代码模式
我痛苦地意识到人们需要在事件驱动的 GUI 代码中编写以下代码模式,其中
private void DoGUISwitch() {
// cruisin for a bruisin' through exception city
object1.Visible = true;
object2.Visible = false;
}
变为:
private void DoGUISwitch() {
if (object1.InvokeRequired) {
object1.Invoke(new MethodInvoker(() => { DoGUISwitch(); }));
} else {
object1.Visible = true;
object2.Visible = false;
}
}
这是 C# 中的一种尴尬模式,无论是记住还是输入。有没有人想出某种捷径或结构来在一定程度上实现自动化?如果有一种方法可以将一个函数附加到执行此检查的对象,而无需执行所有这些额外的工作(例如 object1.InvokeIfNecessary.visible = true
类型快捷方式),那就太酷了。
之前的答案讨论了仅调用 Invoke() 的不切实际每次,即使这样,Invoke() 语法也效率低下,而且仍然难以处理。
那么,有人想出什么捷径吗?
I have become painfully aware of just how often one needs to write the following code pattern in event-driven GUI code, where
private void DoGUISwitch() {
// cruisin for a bruisin' through exception city
object1.Visible = true;
object2.Visible = false;
}
becomes:
private void DoGUISwitch() {
if (object1.InvokeRequired) {
object1.Invoke(new MethodInvoker(() => { DoGUISwitch(); }));
} else {
object1.Visible = true;
object2.Visible = false;
}
}
This is an awkward pattern in C#, both to remember, and to type. Has anyone come up with some sort of shortcut or construct that automates this to a degree? It'd be cool if there was a way to attach a function to objects that does this check without having to go through all this extra work, like a object1.InvokeIfNecessary.visible = true
type shortcut.
Previous answers have discussed the impracticality of just calling Invoke() every time, and even then the Invoke() syntax is both inefficient and still awkward to deal with.
So, has anyone figured out any shortcuts?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(9)
您永远不应该编写如下所示的代码:
如果您确实有如下所示的代码,则您的应用程序不是线程安全的。这意味着您的代码已经从不同的线程调用 DoGUISwitch() 。现在检查它是否在不同的线程中已经太晚了。在调用 DoGUISwitch 之前,必须调用 InvokeRequire。您不应该从不同的线程访问任何方法或属性。
参考:控件。调用必需属性
您可以在其中阅读以下内容:
在单 CPU 架构中没有问题,但在多 CPU 架构中,您可以将部分 UI 线程分配给运行调用代码的处理器...并且如果该处理器与 UI 线程所在的处理器不同然后,当调用线程结束时,Windows 会认为 UI 线程已结束,并将终止应用程序进程,即您的应用程序将退出而不会出现错误。
You should never be writing code that looks like this:
If you do have code that looks like this then your application is not thread-safe. It means that you have code which is already calling DoGUISwitch() from a different thread. It's too late to be checking to see if it's in a different thread. InvokeRequire must be called BEFORE you make a call to DoGUISwitch. You should not access any method or property from a different thread.
Reference: Control.InvokeRequired Property
where you can read the following:
In a single CPU architecture there's no problem, but in a multi-CPU architecture you can cause part of the UI thread to be assigned to the processor where the calling code was running...and if that processor is different from where the UI thread was running then when the calling thread ends Windows will think that the UI thread has ended and will kill the application process i.e. your application will exit without error.
Lee的方法可以进一步简化
并且可以像这样调用
不需要将控件作为参数传递给委托。 C# 自动创建一个闭包。
如果必须返回一个值,可以使用以下实现:
UPDATE:
根据其他几位海报,
Control
可以概括为ISynchronizeInvoke
:DonBoitnott 指出与
Control
不同,ISynchronizeInvoke
接口需要Invoke
方法的对象数组作为action
的参数列表。根据 ISynchronizeInvoke.Invoke(Delegate, Object[] )方法文档,如果不需要参数,我们可以传递
null
。更新2
Mike de Klerk建议的编辑(请参阅第一个代码片段中的插入点注释):
请参阅ToolmakerSteve 的 和 nawfal 在下面的评论中表达了对此建议的担忧。
Lee's approach can be simplified further
And can be called like this
There is no need to pass the control as parameter to the delegate. C# automatically creates a closure.
If you must return a value, you can use this implementation:
UPDATE:
According to several other posters
Control
can be generalized asISynchronizeInvoke
:DonBoitnott pointed out that unlike
Control
theISynchronizeInvoke
interface requires an object array for theInvoke
method as parameter list for theaction
.According to the ISynchronizeInvoke.Invoke(Delegate, Object[]) Method documentation we can pass
null
if no arguments are needed.UPDATE 2
Edits suggested by Mike de Klerk (see comment in 1st code snippet for insert point):
See ToolmakerSteve's and nawfal's comments below for concerns about this suggestion.
您可以编写一个扩展方法:
并像这样使用它:
编辑:正如 Simpzon 在评论中指出的那样,您还可以将签名更改为:
You could write an extension method:
And use it like this:
EDIT: As Simpzon points out in the comments you could also change the signature to:
这是我在所有代码中使用的表单。
我基于博客条目 此处。这种方法并没有让我失望,所以我认为没有理由通过检查
InvokeRequired
属性来使我的代码复杂化。希望这有帮助。
Here's the form I've been using in all my code.
I've based this on the blog entry here. I have not had this approach fail me, so I see no reason to complicate my code with a check of the
InvokeRequired
property.Hope this helps.
创建一个 ThreadSafeInvoke.snippet 文件,然后您只需选择更新语句,右键单击并选择“Surround With...”或 Ctrl-K+S:
Create a ThreadSafeInvoke.snippet file, and then you can just select the update statements, right click and select 'Surround With...' or Ctrl-K+S:
这是李、奥利弗和斯蒂芬答案的改进/组合版本。
该模板允许灵活且无需转换的代码,其可读性更高,而专用委托则提供了效率。
Here's an improved/combined version of Lee's, Oliver's and Stephan's answers.
The template allows for flexible and cast-less code which is much more readable while the dedicated delegate provides efficiency.
我宁愿使用方法委托的单个实例,而不是每次都创建一个新实例。
就我而言,我曾经显示来自 Backroundworker 从 SQL 实例复制和转换大数据的进度和(信息/错误)消息。每当大约 70000 个进度和消息调用之后,我的表单就会停止工作并显示新消息。
当我开始使用单个全局实例委托时,这种情况没有发生。
I'd rather use a single instance of a method Delegate instead of creating a new instance every time.
In my case i used to show progress and (info/error) messages from a Backroundworker copying and casting large data from a sql instance. Everywhile after about 70000 progress and message calls my form stopped working and showing new messages.
This didn't occure when i started using a single global instance delegate.
用法:
代码:
Usage:
Code:
我有点喜欢做一点不同的事情,如果需要的话,我喜欢用一个动作来称呼“我自己”,
这是一个方便的模式,IsFormClosing 是一个字段,当我关闭表单时,我将其设置为 True,因为可能有一些仍在运行的后台线程...
I Kind of like to do it a bit different, i like to call "myself" if needed with an Action,
this is a handy pattern, the IsFormClosing is a field that i set to True when I am closing my form as there might be some background threads that are still running...