如何正确处理跨线程事件并通过 BeginInvoke 和 BackgroundWorker 更新标签
本文解释了如何使用跨线程事件处理、委托、BeginInvoke 和 BackgroundWorker 来更新标签。
引言
本文提出了一种在使用跨线程方法调用时更新 Windows 窗体组件的解决方案。
自 .NET Framework 2.0 以来,您不能显式调用 Windows 窗体组件的方法(例如,您想要更改标签的文本)。 发生这种情况是因为 Windows 窗体不是线程安全的(换句话说,它不能保证使用线程的行为与仅使用主程序线程的行为相同)。
那么,当您的漂亮、多核优化的代码有一个需要从任何线程更新的用户界面时,该怎么办? 您必须使用 BeginInvoke
方法,以便进行线程安全调用。
本文主要涵盖三个方面
- 创建和处理自定义事件
- 跨线程方法调用
BackgroundWorker
类
注意:英语不是我的母语,因此文本有时可能不准确。
背景
要理解此代码,我建议您至少有一些 C# 语言的接触(越多越好)。
如果您以前使用过事件处理,本教程可能会提高您的技能。
如果您从未有机会或耐心学习如何实现自定义事件或如何将其与多个线程一起使用,那么这特别适合您。
Using the Code
该代码已完全记录。 但是对于那些匆忙并寻求一些指针和建议的人,我将给出一个简短的解释。
该代码主要由两个类组成
- Door.cs
- Form1.cs
Door
类具有委托和事件声明。 我选择包含一个 BackgroundWorker
,以便在将 Door
状态从关闭更改为打开以及反之亦然之间插入一些延迟。
Form1
类是一个 Windows 窗体,带有两个按钮(打开门和关闭门)和一个标签来显示当前状态。 门启动时关闭,因此您必须单击 Open
以引发两个事件。
引发的第一个事件是告诉门正在打开的事件。 第二个事件在门打开时引发(当后台工作程序完成其工作时)。
按照设计,您无法关闭未打开的门,也无法打开未关闭的门(即使您单击另一个按钮,也无法更改中间事件)。
现在是一些代码乐趣。
以下代码位于 Door
类中
// event raised before the door opens
public delegate void OpeningDoor ();
public event OpeningDoor RaiseDoorOpening;
// event raised after the door is open
public delegate void OpenDoor ();
public event OpenDoor RaiseDoorOpened;
// event raised before the door closes
public delegate void ClosingDoor ();
public event ClosingDoor RaiseDoorClosing;
// event raised after the door is closed
public delegate void ClosedDoor ();
public event ClosedDoor RaiseDoorClosed;
现在是 Door
类的 Open
方法
public void Open ()
{
// Asserts that the door isn't already open
if ( !_isOpen )
{
RaiseDoorOpening (); // raises event before opening door
#region Open door delay
// we're going to insert a small asynchronous delay here,
// so we can see both events happening.
// This has to be done in another thread, otherwise it would
// freeze the Form (and we don't want that)
BackgroundWorker delayer = new BackgroundWorker ();
delayer.DoWork += new DoWorkEventHandler ( OpenDelayer_DoDelay );
delayer.RunWorkerAsync ();
#endregion
}
else
return; // aborts if already open
}
private void OpenDelayer_DoDelay ( object sender, DoWorkEventArgs e )
{
Thread.Sleep ( 3000 );
_isOpen = true; // sets the door to open state
RaiseDoorOpened (); // Raise the event after the delay happens
}
至于 Form1
类,相关的部分是
构造函数
public Form1 ()
{
InitializeComponent ();
// creates an instance of the Door class
door = new Door ();
// event handler for door opening
door.RaiseDoorOpening += new Door.OpeningDoor(door_OnDoorOpening);
// event handler for door opened
door.RaiseDoorOpened += new Door.OpenDoor ( door_OnDoorOpen );
// event handler for door closing
door.RaiseDoorClosing += new Door.ClosingDoor(door_OnDoorClosing);
// event handler for door closed
door.RaiseDoorClosed += new Door.ClosedDoor ( door_OnDoorClosed );
}
现在是用于打开门的事件处理程序
private void door_OnDoorOpening ()
{
string msg = "Opening the door...";
Console.WriteLine ( msg );
object [] p = GetInvokerParameters ( msg, Color.Orange );
BeginInvoke ( new UpdateDoorStatusLabel ( UpdateLabelText ), p );
}
private void door_OnDoorOpen ()
{
string msg = "Door opened.";
Console.WriteLine ( msg );
object [] p = GetInvokerParameters ( msg, Color.Green );
BeginInvoke ( new UpdateDoorStatusLabel ( UpdateLabelText ), p );
}
用于为委托创建所需参数的 Helper 方法
private object [] GetInvokerParameters (string labelMessage,
Color labelColor )
{
// We have to create an object array as this is the only way our
// UpdateLabelText method can receive the parameters
object [] delegateParameter = new object [ 2 ];
delegateParameter [ 0 ] = labelMessage;
delegateParameter [ 1 ] = labelColor;
return delegateParameter;
}
用于更新文本并更改 forecolor
的 Helper 方法
private void UpdateLabelText ( string text, Color color )
{
lblDoorStatus.Text = text;
lblDoorStatus.ForeColor = color;
}
Open
按钮 Click 事件处理程序
private void btnOpenDoor_Click ( object sender, EventArgs e )
{
door.Open ();
}
关注点
此代码帮助我记住了如何在 C# 中使用委托以及如何处理事件。 我还学习了如何使用 BackgroundWorker
来使延迟正常工作,并且跨线程调用是一个奖励(当我编写事件处理时,我一开始并没有期望使用它)。
历史
- 2008-02-09 - 1.0 - 首次发布
涵盖Door
类中的Opening
、Open
、Closing
、Close
的事件。