调用函数的最佳方法是啥?

Posted

技术标签:

【中文标题】调用函数的最佳方法是啥?【英文标题】:What is the best way to invoke a function?调用函数的最佳方法是什么? 【发布时间】:2016-09-19 18:36:30 【问题描述】:

我的程序有 2 个线程正在运行,线程 1 执行一些操作来控制在线程 2 上运行的表单中的标签。所以我必须使用委托并调用表单 1 类中的函数来访问标签。我的代码在下面,它运行良好。但是,我想知道是否有更短、更好的方法来做到这一点?

delegate void Change_Status_Call_Back(string status_changed);
    public void change_status(string status_changed)
    
        if (this.label_status.InvokeRequired)
        
            Change_Status_Call_Back obj = new Change_Status_Call_Back(change_status);
            this.Invoke(obj, new object[]  status_changed );
        
        else
        
            this.label_status.Text = status_changed;
        
    

【问题讨论】:

在code review 上工作代码更好。您可以查看我提供的 answer 以使检查不那么冗长。这个问题也有各种不同的使用方法,最终只是偏好问题。 【参考方案1】:

这个问题“主要基于意见”。不过,你已经触动了我的一个小毛病,所以……

您应该完全跳过InvokeRequired 检查:

public void change_status(string status_changed)

    this.Invoke((MethodInvoker)(() => this.label_status.Text = status_changed));

无论如何,框架必须有效地检查InvokeRequired,因为它需要支持在UI线程上调用而不会死锁。所以检查你的代码是多余的。在这样的 UI 代码中始终将方法主体包装在委托调用中的开销是无关紧要的,尤其是因为如果您正在编写此代码,则可能在 InvokeRequired 为真时该方法不会被称为异常无论如何(即永远不会采用“快速路径”)。

更好的是使用更现代的机制来处理跨线程访问,例如async/awaitProgress<T> 类。然后,您根本不必编写对Invoke() 的显式调用。

前段时间,我在这里更深入地咆哮:MSDN’s canonical technique for using Control.Invoke is lame

【讨论】:

【参考方案2】:

我会这样做:

public void change_status(string status_changed)

    this.label_status.InvokeSafely(c => c.Text = status_changed);

你需要这个扩展方法:

    public static void InvokeSafely(this Control control, Action<Control> action)
    
        if (control.InvokeRequired)
        
            control.Invoke((Action)(() => action?.Invoke(control)));
        
        else
        
            action?.Invoke(control);
        
    

【讨论】:

【参考方案3】:

环顾四周,我想出了这个:

// UPDATE DISPLAY items (using Invoke in case running on BW thread).
IAsyncResult h = BeginInvoke((MethodInvoker)delegate

    FooButton.Text = temp1;
    BarUpdown.Value = temp2;

);
EndInvoke(h);  // Wait for invoke to complete.
h.AsyncWaitHandle.Close();  // Explicitly close the wait handle.
                            // (Keeps handle count from growing until GC.)

详情:

我完全删除了if (InvokeRequired)。 (从 Peter Duniho 的回答中发现。) Invoke() 在 UI 线程上工作得很好。在仅在 UI 线程上运行的代码中,UI 操作不需要特殊处理。在仅在非 UI 线程上运行的代码中,将所有 UI 操作包装在 Invoke() 中。在可以在 UI 线程或非 UI 线程上运行的代码中,同样将所有 UI 操作包装在 Invoke() 中。在 UI 线程上运行时始终使用 Invoke() 会增加一些开销,但是:开销不大(我希望);无论如何,这些操作在 UI 线程上运行的频率较低;并且始终使用 Invoke,您不必为 UI 操作编写两次代码。我被卖了。 我将Invoke() 替换为BeginInvoke() .. EndInvoke() .. AsyncWaitHandle.Close()。 (找到elsewhere。)Invoke() 可能只是BeginInvoke() .. EndInvoke(),所以这只是内联扩展(目标代码稍多;执行速度稍快)。添加AsyncWaitHandle.Close() 可以解决其他问题:在非UI 线程上运行时,Invoke() 会留下数百个句柄,这些句柄在垃圾回收之前一直存在。 (在任务管理器中看到句柄数量增加是很可怕的。)使用BeginInvoke() .. EndInvoke() 使挥之不去的句柄保持不变。 (惊喜:仅使用BeginInvoke() 不会留下句柄;看起来EndInvoke() 是罪魁祸首。)使用AsyncWaitHandle.Close() 显式杀死死句柄消除了挥之不去的句柄的[化妆品] 问题。在 UI 线程上运行时,BeginInvoke() .. EndInvoke()(如Invoke())没有句柄,因此AsyncWaitHandle.Close() 是不必要的,但我认为它也不会花费太多。 IsDisposed 测试在竞争条件下似乎是安全的,但我认为没有必要。我担心 BackgroundWorker 可以 Invoke() 操作;当它处于挂起状态时,单击可以触发 UI 线程上的回调,该回调可以 Close() 表单,然后消息循环执行此操作。 (不确定这是否会发生。)

问题:(我会在这里更新,当某些东西有效时。)我将我所有的 UI 更新从在 UI 计时器 kludge 上运行更改为使用 Invoke() (如上),现在关闭表单在大约 20 的竞争条件下失败% 的时间。如果用户单击停止我的后台工作人员,则在此之后单击关闭可以正常工作。但是,如果用户直接单击关闭,则会触发 UI 线程上的回调,该回调关闭()表单;触发另一个标记后台工作人员停止的;后台工作人员继续,它在 EndInvoke() 处崩溃,说“无法访问已处置的对象。对象名称:'MainWin'。在 System.Windows.Forms.Control.MarshaledInvoke(Control caller, Delegate method, Object[] args, Boolean同步)...”。在EndInvoke() .. AsyncWaitHandle.Close() 周围添加if (!this.IsDisposed) 并不能解决问题。

选项:返回使用表单计时器:让 BW 将其更改写入十几个全局“邮箱”变量。让计时器执行FooButton.Text = nextFooButtonText; 等。大多数此类分配几乎什么都不做,因为设置表单字段只会在值实际更改时更新显示。 (为了清晰和减少复制对象,将邮箱变量初始化为null,并让计时器做if (nextFooButtonText != null) FooButton.Text = nextFooButtonText; nextFooButtonText = null; 等。)计时器每隔这么多毫秒就会在UI消息循环上放置一个新事件,这比磨更傻Invoke()s。在计时器回调上更新显示会使每次更新延迟 [最多] 计时器间隔。 (呸。)

工作选项:仅使用BeginInvoke()。为什么要让 BW 等待每个 Invoke 完成? 1) temp1 和 temp2 似乎作为引用传递 - 如果它们在 BeginInvoke() 之后发生更改,则新值获胜。 (但这还不错。) 2) temp1 和 temp2 可能超出范围。 (但是在最后一个引用消失之前,它们不安全不会被释放吗?) 3) 等待确保 BW 一次只有一个调用的操作挂起 - 如果 UI 线程阻塞一段时间,BW 无法将其埋入事件。 (但我的 UI 线程不能阻塞,至少在我的 BW 运行时不会阻塞。)

选项:将try .. catch 放在EndInvoke() 周围。 (呸。)

我看到了其他几个建议的技巧: • 让Close 自行取消,启动计时器,然后返回,以便在UI 线程上完成任何延迟的Invoke();不久之后,计时器回调执行了真正的关闭(找到 here;来自 here)。 •杀死后台工作线程。 •更改 Program.cs 以不同方式关闭。

【讨论】:

以上是关于调用函数的最佳方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

从 C 调用 Swift 的最佳方法是啥?

将参数传递给从 R 中的字符串调用的用户定义函数的最佳方法是啥?

在函数调用中发送 QStrings 的最佳方式是啥?

从另一个脚本调用脚本的最佳方法是啥?

在函数结束(例如检查失败)之前在 python 中退出函数(没有返回值)的最佳方法是啥?

当我已经返回一个值时,从函数返回错误的最佳方法是啥?