自动执行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 = truetypes的快捷方式,那将会很酷。

以前的答案已经讨论过每次只调用Invoke()的不切实际的情况,即使如此,Invoke()语法也是效率低下,处理起来还是尴尬的。

那么,有没有人找出任何捷径?

李的方法可以进一步简化

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

不需要将控件作为parameter passing给委托。 C#自动创build一个闭包 。


更新

根据其他几个海报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 Klerkbuild议的编辑(参见插入点的第一个代码片段的注释):

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

有关此build议的疑虑,请参见下面的ToolmakerSteve的评论。

你可以写一个扩展方法:

 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属性的检查。

希望这可以帮助。

创build一个ThreadSafeInvoke.snippet文件,然后你可以select更新语句,右键单击并select'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 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; }); 

我宁愿使用方法Delegate的单个实例,而不是每次创build一个新的实例。 在我的情况下,我用来显示进度和(信息/错误)消息从一个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); } 
 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 => obj.InvokeRequired ? (TOut)obj.Invoke(func, new object[] { obj }) : func(obj); } 

你永远不应该写这样的代码:

 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(如果控件的句柄已经创build)。

在单个CPU体系结构中没有问题,但是在多CPU体系结构中,可以使部分UI线程分配给正在运行调用代码的处理器……并且如果该处理器与UI线程的位置不同当调用线程结束时,Windows会认为UI线程已经结束,并将终止应用程序进程,也就是说,您的应用程序将无错地退出。

我有点喜欢做这个有点不同,我喜欢打电话“我自己”,如果需要与行动,

  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时,我正在closures我的forms,因为可能有一些后台线程仍在运行…