清理使用InvokeRequired乱丢的代码

Eri*_*bes 46 c# multithreading invoke invokerequired

我知道,当从任何非UI线程操作UI控件时,您必须封送对UI线程的调用以避免问题.一般的共识是您应该使用测试InvokeRequired,如果为true,则使用.Invoke来执行封送处理.

这会导致很多代码看起来像这样:

private void UpdateSummary(string text)
{
    if (this.InvokeRequired)
    {
        this.Invoke(new Action(() => UpdateSummary(text)));
    }
    else
    {
        summary.Text = text;
    }
}
Run Code Online (Sandbox Code Playgroud)

我的问题是:我可以省略InvokeRequired测试并只调用Invoke,如下所示:

private void UpdateSummary(string text)
{
    this.Invoke(new Action(() => summary.Text = text));
}
Run Code Online (Sandbox Code Playgroud)

这样做有问题吗?如果是这样,是否有更好的方法来保持InvokeRequired测试,而不必在整个地方复制和粘贴此模式?

Joh*_*zen 65

那怎么样:

public static class ControlHelpers
{
    public static void InvokeIfRequired<T>(this T control, Action<T> action) where T : ISynchronizeInvoke
    {
        if (control.InvokeRequired)
        {
            control.Invoke(new Action(() => action(control)), null);
        }
        else
        {
            action(control);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

像这样使用它:

private void UpdateSummary(string text)
{
    summary.InvokeIfRequired(s => { s.Text = text });
}
Run Code Online (Sandbox Code Playgroud)

  • @SLaks:嗯,你打了我3秒,但我得到了代码.:) (9认同)
  • 我做了完全相同的事情,除了我还添加了`public static T InvokeIfRequired(this Control control,Func <T> function)`以及当我想要返回一个值时. (4认同)

SLa*_*aks 8

Invoke从UI线程调用有点效率低下.

相反,您可以创建一个InvokeIfNeededAction参数的扩展方法.(这也可以让你new Action(...)从呼叫网站中删除)

  • 是什么让UI线程的`Invoke`效率低下? (4认同)
  • SLaks:`Invoke`是同步的,如果它试图与UI线程上的消息队列进行交互,它不会死锁吗? (2认同)
  • SLaks:你说`Invoke`导致你的动作在任何其他排队调用之后执行,而不是在其他排队调用之前执行.如何更改订单会降低效率? (2认同)

Mic*_*ael 7

我一直在阅读关于添加逻辑检查来反复讨论的问题,以确定在不在UI线程上而不在UI线程本身上时是否应该使用IFF调用.我写了一个类来检查执行(通过秒表)各种方法的时间,以粗略估计一种方法相对于另一种方法的效率.

对于你们中的一些人来说结果可能会令人惊讶(这些测试是通过Form.Shown事件运行的):

     // notice that we are updating the form's title bar 10,000 times
     // directly on the UI thread
     TimedAction.Go
     (
        "Direct on UI Thread",
        () =>
        {
           for (int i = 0; i < 10000; i++)
           {
              this.Text = "1234567890";
           }
        }
     );

     // notice that we are invoking the update of the title bar
     // (UI thread -> [invoke] -> UI thread)
     TimedAction.Go
     (
        "Invoke on UI Thread",
        () =>
        {
           this.Invoke
           (
              new Action
              (
                 () =>
                 {
                    for (int i = 0; i < 10000; i++)
                    {
                       this.Text = "1234567890";
                    }
                 }
              )
           );
        }
     );

     // the following is invoking each UPDATE on the UI thread from the UI thread
     // (10,000 invokes)
     TimedAction.Go
     (
        "Separate Invoke on UI Thread",
        () =>
        {
           for (int i = 0; i < 10000; i++)
           {
              this.Invoke
              (
                 new Action
                 (
                    () =>
                    {
                       this.Text = "1234567890";
                    }
                 )
              );
           }
        }
     );
Run Code Online (Sandbox Code Playgroud)

结果如下:

  • TimedAction :: Go()+ 0 - 调试:[DEBUG]秒表[在UI线程上直接]:300ms
  • TimedAction :: Go()+ 0 - 调试:[DEBUG]秒表[在UI线程上调用]:299ms
  • TimedAction :: Go()+ 0 - 调试:[DEBUG]秒表[在UI线程上单独调用]:649ms

我的结论是,您可以随时安全地调用,无论您是在UI线程还是工作线程,都没有通过消息泵循环回来的显着开销.但是,在UI线程上执行大部分工作而不是多次调用UI线程(通过Invoke())是有利的并且大大提高了效率.

  • ...所以在你使用的任何平台上调用大约35微秒的成本.我认为对于一些人而言,这是巨大的,对于许多其他人来说,这是微不足道的.不错的实验. (5认同)

Mar*_*off 5

我意识到已经有一个答案,但是我也想发布它(我也在这里发布).

我的稍微不同,它可以稍微更安全地处理空控件,并在必要时返回结果.当尝试调用在父窗体上显示可能为null的MessageBox并返回显示MessageBox的DialogResult时,这两个对我来说都派上了用场.


using System;
using System.Windows.Forms;

/// <summary>
/// Extension methods acting on Control objects.
/// </summary>
internal static class ControlExtensionMethods
{
    /// <summary>
    /// Invokes the given action on the given control's UI thread, if invocation is needed.
    /// </summary>
    /// <param name="control">Control on whose UI thread to possibly invoke.</param>
    /// <param name="action">Action to be invoked on the given control.</param>
    public static void MaybeInvoke(this Control control, Action action)
    {
        if (control != null && control.InvokeRequired)
        {
            control.Invoke(action);
        }
        else
        {
            action();
        }
    }

    /// <summary>
    /// Maybe Invoke a Func that returns a value.
    /// </summary>
    /// <typeparam name="T">Return type of func.</typeparam>
    /// <param name="control">Control on which to maybe invoke.</param>
    /// <param name="func">Function returning a value, to invoke.</param>
    /// <returns>The result of the call to func.</returns>
    public static T MaybeInvoke<T>(this Control control, Func<T> func)
    {
        if (control != null && control.InvokeRequired)
        {
            return (T)(control.Invoke(func));
        }
        else
        {
            return func();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

用法:

myForm.MaybeInvoke(() => this.Text = "Hello world");

// Sometimes the control might be null, but that's okay.
var dialogResult = this.Parent.MaybeInvoke(() => MessageBox.Show(this, "Yes or no?", "Choice", MessageBoxButtons.YesNo));
Run Code Online (Sandbox Code Playgroud)