winforms 自动化InvokeRequired代码模式

tzcvj98z  于 2023-02-05  发布在  其他
关注(0)|答案(9)|浏览(274)

我痛苦地意识到,人们需要在事件驱动的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()语法也是低效的,而且 * 仍然 * 难以处理。
有人想出什么捷径了吗?

vlurs2pr

vlurs2pr1#

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) {
        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下面的评论,了解对该建议的关注。

r8uurelv

r8uurelv2#

你可以写一个扩展方法:

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
ghhkc1vu

ghhkc1vu3#

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

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

我是基于博客条目here,我从未遇到过这种方法失败的情况,所以我认为没有理由通过检查InvokeRequired属性来使代码复杂化。
希望这个有用。

xqk2d5yq

xqk2d5yq4#

创建一个ThreadSafeInvoke.snippet文件,然后您只需选择update语句,右键单击并选择“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>
qyyhg6bp

qyyhg6bp5#

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

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

wmvff8tz6#

用法:

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

bq9c1y667#

我宁愿使用方法Delegate的单个示例,而不是每次都创建一个新示例。在我的例子中,我曾经显示Backroundworker从SQL示例复制和转换大量数据的进度和(信息/错误)消息。每隔一段时间,在大约70000次进度和消息调用后,我的窗体停止工作并显示新消息。当我开始使用单个全局示例Delegate时,这种情况并没有发生。

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

gupuwyp28#

我有点喜欢做一点不同的,我喜欢叫“我自己”,如果需要一个动作,

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

pdtvr36n

pdtvr36n9#

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

private void DoGUISwitch() {
    if (object1.InvokeRequired) {
        object1.Invoke(new MethodInvoker(() => { DoGUISwitch(); }));
    } else {
        object1.Visible = true;
        object2.Visible = false;
    }
}

如果你的代码看起来像这样,那么你的应用程序不是线程安全的。这意味着你的代码已经从另一个线程调用了DoGUISwitch()。现在检查它是否在另一个线程中已经太晚了。必须在你调用DoGUISwitch之前调用InvokeRequire。你不应该从另一个线程访问任何方法或属性。
参考:Control.InvokeRequired Property,您可以从中读取以下内容:
除了InvokeRequired属性之外,控件上还有四个方法可以线程安全地调用:Invoke、BeginInvoke、EndInvoke和CreateGraphics(如果已创建控件的句柄)。
在单CPU架构中没有问题,但在多CPU架构中,您可以将部分UI线程分配给运行调用代码的处理器...如果该处理器与运行UI线程的处理器不同,则当调用线程结束时,Windows将认为UI线程已经结束,并将终止应用程序进程,即您的应用程序将无错误地退出。

相关问题