199

我已经痛苦地意识到需要在事件驱动的 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() 语法既低效又难以处理。

那么,有没有人想出捷径呢?

4

9 回答 9

151

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

有关此建议的疑虑,请参阅下方ToolmakerStevenawfal 的评论。

于 2012-08-29T13:44:20.443 回答
138

你可以写一个扩展方法:

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
于 2010-03-02T23:37:09.557 回答
40

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

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

我基于这里的博客条目。我没有让这种方法失败,所以我认为没有理由通过检查InvokeRequired属性来使我的代码复杂化。

希望这可以帮助。

于 2010-03-03T00:05:28.887 回答
10

创建一个 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>
于 2011-02-15T13:42:56.097 回答
8

这是李、奥利弗和斯蒂芬的答案的改进/组合版本。

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;
});
于 2015-04-07T17:37:26.447 回答
6

我宁愿使用方法 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);
}
于 2013-06-21T11:20:18.383 回答
6

用法:

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);
        }
    }
}
于 2017-10-02T04:13:08.397 回答
3

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

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

于 2016-12-21T22:07:11.677 回答
-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 线程已经结束并将终止应用程序进程,即您的应用程序将退出而不会出错。

于 2014-10-29T13:10:30.227 回答