提问人:Tom Corelis 提问时间:3/3/2010 最后编辑:CommunityTom Corelis 更新时间:2/1/2023 访问量:171987
自动化 InvokeRequired Code Pattern
Automating the InvokeRequired code pattern
问:
我痛苦地意识到,在事件驱动的 GUI 代码中,需要编写以下 Code Pattern 的频率,其中
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() 语法既低效又难以处理。
那么,有没有人想出任何捷径呢?
答:
你可以写一个扩展方法:
public static void InvokeIfRequired(this Control c, Action<Control> action)
{
if(c.InvokeRequired)
{
c.Invoke(new Action(() => action(c)));
}
else
{
action(c);
}
}
并像这样使用它:
object1.InvokeIfRequired(c => { c.Visible = true; });
编辑:正如 Simpzon 在评论中指出的那样,您还可以将签名更改为:
public static void InvokeIfRequired<T>(this T c, Action<T> action)
where T : Control
评论
这是我在所有代码中使用的表单。
private void DoGUISwitch()
{
Invoke( ( MethodInvoker ) delegate {
object1.Visible = true;
object2.Visible = false;
});
}
我基于这里的博客文章。我没有让这种方法让我失望,所以我认为没有理由通过检查属性来使我的代码复杂化。InvokeRequired
希望这会有所帮助。
评论
InvokeRequired
创建一个 ThreadSafeInvoke.snippet 文件,然后只需选择更新语句,右键单击并选择“Surround With...”。或 Ctrl-K+S:
<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippet Format="1.0.0" xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
<Header>
<Title>ThreadsafeInvoke</Title>
<Shortcut></Shortcut>
<Description>Wraps code in an anonymous method passed to Invoke for Thread safety.</Description>
<SnippetTypes>
<SnippetType>SurroundsWith</SnippetType>
</SnippetTypes>
</Header>
<Snippet>
<Code Language="CSharp">
<![CDATA[
Invoke( (MethodInvoker) delegate
{
$selected$
});
]]>
</Code>
</Snippet>
</CodeSnippet>
Lee的方法可以进一步简化
public static void InvokeIfRequired(this Control control, MethodInvoker action)
{
// See Update 2 for edits Mike de Klerk suggests to insert here.
if (control.InvokeRequired) {
control.Invoke(action);
} else {
action();
}
}
并且可以这样称呼
richEditControl1.InvokeIfRequired(() =>
{
// Do anything you want with the control here
richEditControl1.RtfText = value;
RtfHelpers.AddMissingStyles(richEditControl1);
});
无需将控件作为参数传递给委托。C# 会自动创建闭包。
如果必须返回值,可以使用以下实现:
private static T InvokeIfRequiredReturn<T>(this Control control, Func<T> function)
{
if (control.InvokeRequired) {
return (T)control.Invoke(function);
} else {
return function();
}
}
更新:
根据其他几张海报可以概括为:Control
ISynchronizeInvoke
public static void InvokeIfRequired(this ISynchronizeInvoke obj,
MethodInvoker action)
{
if (obj.InvokeRequired) {
var args = new object[0];
obj.Invoke(action, args);
} else {
action();
}
}
DonBoitnott 指出,与接口不同,该方法需要一个对象数组作为 .Control
ISynchronizeInvoke
Invoke
action
更新 2
Mike de Klerk 建议的编辑(请参阅插入点的第一个代码片段中的注释):
// When the form, thus the control, isn't visible yet, InvokeRequired returns false,
// resulting still in a cross-thread exception.
while (!control.Visible)
{
System.Threading.Thread.Sleep(50);
}
请参阅下面的 ToolmakerSteve 和 nawfal 的评论,了解对此建议的担忧。
评论
ISynchronizeInvoke
Control
)
while (!control.Visible) ..sleep..
Sleep
Thread.Sleep
When the form, thus the control, isn't visible yet, InvokeRequired returns false, resulting still in a cross-thread exception.
if (c.InvokeRequired) c.Invoke(foo); else foo();
我宁愿使用方法 Delegate 的单个实例,而不是每次都创建一个新实例。 就我而言,我曾经显示来自 Backroundworker 的进度和(信息/错误)消息,从 sql 实例复制和投射大数据。在大约 70000 次进度和消息调用之后,我的表单停止工作并显示新消息。 当我开始使用单个全局实例委托时,没有发生这种情况。
delegate void ShowMessageCallback(string message);
private void Form1_Load(object sender, EventArgs e)
{
ShowMessageCallback showMessageDelegate = new ShowMessageCallback(ShowMessage);
}
private void ShowMessage(string message)
{
if (this.InvokeRequired)
this.Invoke(showMessageDelegate, message);
else
labelMessage.Text = message;
}
void Message_OnMessage(object sender, Utilities.Message.MessageEventArgs e)
{
ShowMessage(e.Message);
}
你永远不应该编写看起来像这样的代码:
private void DoGUISwitch() {
if (object1.InvokeRequired) {
object1.Invoke(new MethodInvoker(() => { DoGUISwitch(); }));
} else {
object1.Visible = true;
object2.Visible = false;
}
}
如果您确实有如下所示的代码,则您的应用程序不是线程安全的。这意味着您的代码已经从其他线程调用了 DoGUISwitch()。现在检查它是否在不同的线程中为时已晚。在调用 DoGUISwitch 之前,必须调用 InvokeRequire。不应从其他线程访问任何方法或属性。
参考:Control.InvokeRequired 属性,您可以在其中读取以下内容:
除了 InvokeRequired 属性之外,还有四个方法 线程安全调用的控件:Invoke、BeginInvoke、EndInvoke 和 CreateGraphics(如果控件的句柄已是 创建。
在单 CPU 架构中,没有问题,但在多 CPU 架构中,可能会导致部分 UI 线程分配给运行调用代码的处理器......如果该处理器与 UI 线程的运行位置不同,则当调用线程结束时,Windows 将认为 UI 线程已结束并将终止应用程序进程,即您的应用程序将退出而不会出错。
评论
invoke()
invoke()
以下是 Lee、Oliver 和 Stephan 答案的改进/组合版本。
public delegate void InvokeIfRequiredDelegate<T>(T obj)
where T : ISynchronizeInvoke;
public static void InvokeIfRequired<T>(this T obj, InvokeIfRequiredDelegate<T> action)
where T : ISynchronizeInvoke
{
if (obj.InvokeRequired)
{
obj.Invoke(action, new object[] { obj });
}
else
{
action(obj);
}
}
该模板允许灵活且无强制转换的代码,其可读性更强,而专用委托则提供了效率。
progressBar1.InvokeIfRequired(o =>
{
o.Style = ProgressBarStyle.Marquee;
o.MarqueeAnimationSpeed = 40;
});
我有点喜欢做一点不同的事情,如果需要的话,我喜欢用“我自己”来称呼自己,
private void AddRowToListView(ScannerRow row, bool suspend)
{
if (IsFormClosing)
return;
if (this.InvokeRequired)
{
var A = new Action(() => AddRowToListView(row, suspend));
this.Invoke(A);
return;
}
//as of here the Code is thread-safe
这是一个方便的模式,IsFormClosing 是我在关闭表单时设置为 True 的字段,因为可能有一些后台线程仍在运行......
用法:
control.InvokeIfRequired(c => c.Visible = false);
return control.InvokeIfRequired(c => {
c.Visible = value
return c.Visible;
});
法典:
using System;
using System.ComponentModel;
namespace Extensions
{
public static class SynchronizeInvokeExtensions
{
public static void InvokeIfRequired<T>(this T obj, Action<T> action)
where T : ISynchronizeInvoke
{
if (obj.InvokeRequired)
{
obj.Invoke(action, new object[] { obj });
}
else
{
action(obj);
}
}
public static TOut InvokeIfRequired<TIn, TOut>(this TIn obj, Func<TIn, TOut> func)
where TIn : ISynchronizeInvoke
{
return obj.InvokeRequired
? (TOut)obj.Invoke(func, new object[] { obj })
: func(obj);
}
}
}
评论
object1.InvokeIfNecessary.Visible = true