自动化invokerrequired代码模式

我已经痛苦地意识到,在事件驱动的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()语法也是低效的,而且仍然难以处理。

有人找到什么捷径了吗?

164705 次浏览

你可以写一个扩展方法:

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属性来使我的代码复杂化。

希望这能有所帮助。

创建一个ThreadSafeInvoke。然后你可以选择更新语句,右键单击并选择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>

李的方法可以进一步简化

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的注释。

我宁愿使用方法Delegate的单个实例,而不是每次都创建一个新实例。 在我的情况下,我用来显示进度和(信息/错误)消息从一个后台工作者复制和铸造大数据从一个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。您不应该从不同的线程访问任何方法或属性。

参考:控制。InvokeRequired财产

除了InvokeRequired属性外,还有四个方法 调用一个线程安全的控件:Invoke, BeginInvoke, EndInvoke 和CreateGraphics(如果控件的句柄已经为) 创建。< / p >

在单CPU架构中没有问题,但在多CPU架构中,你可以将部分UI线程分配给运行调用代码的处理器……如果这个处理器与UI线程运行时的处理器不同,那么当调用线程结束时,Windows将认为UI线程已经结束,并将杀死应用程序进程,即你的应用程序将正常退出。

以下是李、奥利弗和斯蒂芬回答的改进版/综合版。

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);
}
}
}