C# 调用函数的最佳方式是什么?
我的程序有两个线程在运行,线程1执行一些操作来控制线程2上运行的表单中的标签。因此,我必须使用委托并调用Form1类中的函数来访问标签。下面是我的代码,它工作得很好。然而,我想知道是否有一个更短,更好的方法来做到这一点C# 调用函数的最佳方式是什么?,c#,multithreading,delegates,invoke,C#,Multithreading,Delegates,Invoke,我的程序有两个线程在运行,线程1执行一些操作来控制线程2上运行的表单中的标签。因此,我必须使用委托并调用Form1类中的函数来访问标签。下面是我的代码,它工作得很好。然而,我想知道是否有一个更短,更好的方法来做到这一点 delegate void Change_Status_Call_Back(string status_changed); public void change_status(string status_changed) { if (this.la
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;
}
}
我会这样做:
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);
}
}
publicstaticvoid安全调用(此控件、操作)
{
if(control.invokererequired)
{
control.Invoke((操作)(()=>Action?.Invoke(控制));
}
其他的
{
动作?调用(控制);
}
}
这个问题“主要基于观点”。尽管如此,你还是触动了我的愤怒,所以
您应该跳过invokererequired
检查:
public void change_status(string status_changed)
{
this.Invoke((MethodInvoker)(() => this.label_status.Text = status_changed));
}
无论如何,框架都必须有效地检查invokererequired
,因为它需要支持在UI线程上进行调用而不会出现死锁。所以签入代码是多余的。在这样的UI代码中,总是在委托调用中包装方法体的开销是无关紧要的,特别是因为如果您正在编写此代码,那么当invokererequired
无论如何都是真的时,该方法可能不会被称为异常(即,无论如何都不会采用“快速路径”)
更好的方法是使用更现代的机制来处理跨线程访问,例如async
/await
或Progress
类。然后,您根本不必编写对Invoke()
的显式调用
不久前,我在这里更深入地咆哮道:环顾四周,我想到了这个:
// 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.)
详情:
- 我完全删除了
。(从Peter Duniho的回答中发现)Invoke()在UI线程上运行良好。在只在UI线程上运行的代码中,UI操作不需要特殊处理。在仅在非UI线程上运行的代码中,将所有UI操作包装在Invoke()中。在可以在UI线程或非UI线程上运行的代码中,同样地,将所有UI操作包装在Invoke()中。在UI线程上运行时,总是使用Invoke()会增加一些开销,但是:开销不大(我希望如此);无论如何,这些操作在UI线程上运行的频率较低;而且通过始终使用Invoke,您不必对UI操作编写两次代码。我被出卖了if(invokererequired)
- 我将
替换为Invoke()
。(已找到。)BeginInvoke()。。EndInvoke()。。AsyncWaitHandle.Close()
可能只是Invoke()
,所以这只是内联扩展(稍微多一些目标代码;稍微快一些执行)。添加BeginInvoke()。。EndInvoke()
可以解决其他问题:在非UI线程上运行时,AsyncWaitHandle.Close()
会留下数百个句柄,直到垃圾回收为止。(看着任务管理器中的句柄数增长是很可怕的。)使用Invoke()
使延迟句柄保持不变。(令人惊讶的是:仅使用BeginInvoke()。。EndInvoke()
不会留下句柄;看起来是BeginInvoke()
造成的。)使用EndInvoke()
显式杀死死句柄消除了残留句柄的[表面]问题。在UI线程上运行时,AsyncWaitHandle.Close()
(比如BeginInvoke()。。EndInvoke()
)没有留下任何句柄,因此Invoke()
是不必要的,但我认为它也不昂贵AsyncWaitHandle.Close()
- IsDisposed测试在比赛条件下似乎是安全的,但我认为它没有必要。我担心BackgroundWorker会调用()操作;当表单处于挂起状态时,单击可以触发UI线程上的回调,从而关闭()表单,然后消息循环执行此操作。(不确定会发生这种情况。)
EndInvoke()周围添加if(!this.IsDisposed){}
。。AsyncWaitHandle.Close()
无法修复它
选项:返回到使用窗体计时器:使BW将其更改写入十几个全局“邮箱”变量。让计时器执行FooButton.Text=nextfoobuttonext代码>,等等。大多数这样的赋值几乎不起任何作用,因为设置表单字段只会在值实际更改时更新显示。(为了清晰和减少复制对象,请将邮箱变量初始化为null,并让计时器执行if(nextfootbuttonext!=null){FooButton.Text=nextfootbuttonext;nextfootbuttonext=null;}
,等等)计时器每隔这么多毫秒在UI消息循环上放置一个新事件,这比调用()更愚蠢)s在计时器回调上更新显示会将每次更新延迟[高达]计时器间隔。(恶心。)
工作选项:仅使用BeginInvoke()
。为什么让BW等待每个调用完成?1) temp1和temp2似乎是作为引用传递的-如果它们在BeginInvoke()
之后被更改,则新值将获胜。(但这并不坏。)2)temp1和temp2可能超出范围。(但是在最后一个引用消失之前它们不会被释放吗?)3)等待确保BW一次只有一个被调用的操作挂起-