自动执行InvokeRequired代码模式


179

我已经痛苦地意识到,需要多长时间在事件驱动的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()语法效率低下,仍然难以处理。

那么,有人知道快捷方式吗?


2
我想知道同一件事,但是关于WPF的Dispatcher.CheckAccess()。
泰勒·莱斯

我想出了一个受你的话启发的相当疯狂的建议object1.InvokeIfNecessary.Visible = true。查看我最新的答案,并让我知道您的想法。
丹涛

1
添加一个代码片段以帮助实现Matt Davis建议的方法:请参阅我的答案(但只是向以后的读者展示;
Aaron Gage

3
我不明白为什么Microsoft在.NET中没有做任何简化。为线程中的表单上的每个更改创建委托确实很烦人。
卡米尔2012年

@Kamil我完全同意!考虑到它的普遍性,这是一种疏忽。在框架内,仅在必要时处理线程。似乎很明显。
SteveCinq '19

Answers:


138

李的方法可以进一步简化

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#自动创建一个闭包


更新

根据其他几个海报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指出,与接口不同,ControlISynchronizeInvoke接口需要该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的评论。


2
ISynchronizeInvoke代替它会更好Control吗?(对Jon Skeetstackoverflow.com/questions/711408/…表示感谢
Odys 2013年

@odyodyodys:好点。我不知道ISynchronizeInvoke。但是从中派生的唯一类型(根据Reflector)是Control,因此优势受到限制。
Olivier Jacot-Descombes 2013年

3
@ mike-de-clerk,我很担心您的建议while (!control.Visible) ..sleep..。对我来说,这是一种不好的代码味道,因为这是潜在的无限延迟(在某些情况下甚至可能是无限循环),在某些代码中,调用者可能不希望这样的延迟(甚至是死锁)。恕我直言,任何使用Sleep均应由每个呼叫者负责,或者应在单独的包装中清楚地标明其后果。恕我直言,通常最好“努力失败”(例外,在测试过程中捕获),或者如果控件未准备好,则“不执行任何操作”。注释?
ToolmakerSteve

1
@ OlivierJacot-Descombes,太好了,如果您请解释一下thread.invokerequired是如何工作的呢?
Sudhir.net

1
InvokeRequired告诉调用线程是否与创建控件的线程不同。Invoke将操作从调用线程传递到执行该操作的控件线程。例如,这可以确保单击事件处理程序永远不会中断。
奥利维尔·雅各布·德斯科姆斯

133

您可以编写一个扩展方法:

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

也许我太笨了,但是此代码无法编译。因此,我将其修复为我自己构建的(VS2008)。
奥利弗

5
仅出于完整性考虑:在WPF中,存在一种不同的调度机制,但其工作原理非常相似。您可以在此处使用此扩展方法:public static void InvokeIfRequired <T>(此T aTarget,Action <T> aActionToExecute),其中T:DispatcherObject {if(aTarget.CheckAccess()){aActionToExecute(aTarget); } else {aTarget.Dispatcher.Invoke(aActionToExecute); }}
Simon D.

1
我添加了一个答案,可以稍微简化Lee的解决方案。
Olivier Jacot-Descombes 2012年

嗨,当我使用类似的东西时,这种通用实现可能会带来很大的问题。如果控件是Dispose / Disposed,则将获得ObjectDisposedException。
Offler

1
@Offler-好吧,如果将它们放置在不同的线程上,则会出现同步问题,这不是此方法中的问题。
李李

33

这是我在所有代码中一直使用的表格。

private void DoGUISwitch()
{ 
    Invoke( ( MethodInvoker ) delegate {
        object1.Visible = true;
        object2.Visible = false;
    });
} 

我已经基于此处的博客条目。我没有让这种方法令我失望,因此我认为没有理由通过检查该InvokeRequired属性来使我的代码复杂化。

希望这可以帮助。


+1-我偶然发现了与您相同的博客条目,并认为这是所有建议的最干净的方法
Tom Bushell

3
使用这种方法会降低性能,当多次调用时可能会堆积。stackoverflow.com/a/747218/724944
surfen 2011年

4
您必须使用InvokeRequired是否可以在显示控件之前执行代码,否则将发生致命异常。
2014年

9

创建一个ThreadSafeInvoke.snippet文件,然后可以选择更新语句,右键单击并选择“环绕...”或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>

6

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

4

我宁愿使用方法委托的单个实例,而不是每次都创建一个新实例。就我而言,我过去常常显示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);
}

3

用法:

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

2

我有点喜欢做一些不同的事情,如果需要,我喜欢用“动作”来称呼“我自己”,

    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的字段,因为可能有一些后台线程仍在运行...


-3

您永远不应编写看起来像这样的代码:

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线程已结束并杀死应用程序进程,即您的应用程序将退出而不会出现错误。


嘿,谢谢您的回答。我问这个问题已经有好几年了(距离我使用C#差不多已经很长时间了),但是我想知道您是否可以进一步解释?您链接的文档引用了invoke()在给控件提供句柄之前调用et等的特定危险,但是IMHO并未描述您所描述的内容。所有这些invoke()废话的全部目的是以线程安全的方式更新UI,我认为在阻塞上下文中放置更多指令会导致结结?(哎呀,很高兴我停止使用M $技术。太复杂了!)
Tom Corelis

我还想指出,尽管经常使用原始代码(
时光

3
我怀疑这个答案是正确的,因为MSDN会像OP给出的那样显示大量示例。
公共无线
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.