自动化 InvokeRequired 代码模式

问题描述 投票:0回答:9

我痛苦地意识到人们需要在事件驱动的 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
类型快捷方式),那就太酷了。

之前的 answers 讨论了每次都调用 Invoke() 的不切实际,即使如此,Invoke() 语法也效率低下且仍然难以处理。

那么,有人想出任何捷径吗?

c# multithreading winforms thread-safety invokerequired
9个回答
161
投票

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

如果必须返回值,可以使用这个实现:

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) {
        obj.Invoke(action, null);
    } else {
        action();
    }
}

DonBoitnott 指出,与

Control
不同,
ISynchronizeInvoke
接口需要
Invoke
方法的对象数组作为
action
的参数列表。

根据 ISynchronizeInvoke.Invoke(Delegate, Object[]) Method 文档,如果不需要参数,我们可以传递

null


更新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 的 评论,了解对此建议的担忧。


140
投票

你可以写一个扩展方法:

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

编辑:正如辛普森在评论中指出的那样,您还可以将签名更改为:

public static void InvokeIfRequired<T>(this T c, Action<T> action) 
    where T : Control

41
投票

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

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

我基于博客条目这里。我没有让这种方法失败,所以我认为没有理由通过检查

InvokeRequired
属性来使我的代码复杂化。

希望这有帮助。


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>

8
投票

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

7
投票

用途:

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

6
投票

我宁愿使用方法委托的单个实例,而不是每次都创建一个新实例。 就我而言,我曾经显示来自 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
投票

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

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


-4
投票

你永远不应该编写如下所示的代码:

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 如果控件的句柄已经被创建,则创建图形 已创建。

在单 CPU 架构中没有问题,但在多 CPU 架构中,您可能会导致部分 UI 线程被分配给运行调用代码的处理器...并且如果该处理器与 UI 所在的处理器不同线程正在运行,然后当调用线程结束时,Windows 会认为 UI 线程已结束,并将终止应用程序进程,即您的应用程序将毫无错误地退出。

© www.soinside.com 2019 - 2024. All rights reserved.