我的程序有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;
}
}
答案 0 :(得分:2)
这个问题是"主要是基于意见的"。不过,你已经触动了我的一个小小的烦恼,所以......
您应该完全跳过InvokeRequired
检查:
public void change_status(string status_changed)
{
this.Invoke((MethodInvoker)(() => this.label_status.Text = status_changed));
}
无论如何,框架必须有效地检查InvokeRequired
,因为它需要支持在UI线程上进行调用而不会发生死锁。因此,检查代码是多余的。在这样的UI代码中,始终将方法体包装在委托调用中的开销是无关紧要的,特别是因为如果您正在编写此代码,则可能的情况是该方法不会无论如何InvokeRequired
都是真的被称为例外(即"快速路径"永远不会被采用)。
更好的方法是使用更现代的机制来处理跨线程访问,例如async
/ await
或Progress<T>
类。然后你根本不必写一个对Invoke()
的显式调用。
前段时间,我在这里进行了更深入的研究:MSDN’s canonical technique for using Control.Invoke is lame
答案 1 :(得分:1)
我会这样做:
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);
}
}
答案 2 :(得分:0)
环顾四周后,我想出了这个:
// 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()中。总是使用Invoke()会在UI线程上运行时增加一些开销,但是:开销不大(我希望);无论如何,这些动作在UI线程上的运行频率较低;并且通过始终使用Invoke,您不必对UI操作进行两次编码。我卖了。Invoke()
替换为BeginInvoke() .. EndInvoke() .. AsyncWaitHandle.Close()
。 (找到elsewhere。)Invoke()
可能只是BeginInvoke() .. EndInvoke()
,所以很多只是内联扩展(稍微多一点的目标代码;稍快一点的执行)。添加AsyncWaitHandle.Close()
解决其他问题:在非UI线程上运行时,Invoke()
会留下数百个句柄,这些句柄会一直存在,直到垃圾回收。 (看到Handles计数在任务管理器中增长是很可怕的。)使用BeginInvoke() .. EndInvoke()
留下挥之不去的句柄。 (惊喜:仅使用BeginInvoke()
不会留下句柄;看起来EndInvoke()
是罪魁祸首。)使用AsyncWaitHandle.Close()
明确杀死死句柄消除了延迟句柄的[整容]问题。在UI线程上运行时,BeginInvoke() .. EndInvoke()
(如Invoke()
)不会留下句柄,因此AsyncWaitHandle.Close()
是不必要的,但我认为它也不贵。问题:(当有些东西工作时我会在这里更新。)我将所有UI更新从在UI计时器kludge上运行改为使用Invoke()(如上所述),现在关闭表单在竞争条件下失败大约20 % 的时间。如果用户单击停止我的后台工作程序,则在该工作正常后单击关闭。但是,如果用户直接点击关闭,则触发UI线程上的回调,其中Close()是表单;触发另一个标记后台工作者停止;后台工作程序继续,它在EndInvoke()崩溃,说“无法访问已处置的对象。对象名称:'MainWin'。在System.Windows.Forms.Control.MarshaledInvoke(控制调用者,委托方法,对象[] args,布尔值同步)......“。在if (!this.IsDisposed) {}
周围添加EndInvoke() .. AsyncWaitHandle.Close()
并不能解决问题。
选项:返回使用表单计时器:使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以不同方式关闭。