代码改变世界

关于自定义通知事件的跨线程问题(转)

2011-12-14 00:03  Andrew.Wangxu  阅读(559)  评论(0编辑  收藏  举报

转自:http://www.cnblogs.com/bluebird2008/archive/2011/04/21/2023648.html

 

[知识背景]

所谓自定义通知事件,就是指在自己的类中定义的事件,该事件用于向调用者发出通知。比如做一个下载工具,下载是需要时间的,用户在界面里点击“下载”之后,我的下载类在后台开启线程开始传输数据,前台界面上可以同时执行其他操作。当数据传输完成,需要通知界面(调用者)已完成下载,以便界面上做相应的改变。这就需要在我的下载类中有类似 DownloadCompleted 的事件,这样在用户的代码中可以通过 downloader.DownloadCompleted += new new EventHandler(XXXXX) 进入他自己的事件处理函数。

这里说的跨线程问题,是指非法的跨线程调用问题。还用上个例子,在下载完成时,需要改变界面中 Label 控件的 Text 属性以提示用户下载完成。这就牵涉到在另一个类所创建的线程中操纵UI线程中创建的控件。这种做法在 .NET 中是不推荐的,同时这样会严重影响代码质量。(可参考MSDN:ms-help://MS.VSCC.v80/MS.MSDN.v80/MS.VisualStudio.v80.chs/dv_fxmclictl/html/138f38b6-1099-4fd5-910c-390b41cbad35.htm)

 

[案例]

自定义一个类似与 Timer 的控件,并实现一个 Tick 事件。调用者在 Tick 事件里操纵界面上的元素。

代码段:

 

Form1.cs 里的代码:

public Form1()
{
InitializeComponent();
MyTimer mytimer = new MyTimer();
mytimer.Tick += new EventHandler(mytimer_Tick);
mytimer.Start();
}

void mytimer_Tick(object sender, EventArgs e)
{

CheckBox check = new CheckBox();
check.Top = this.Controls.Count * check.Height;
this.Controls.Add(check);
}

 

MyTimer.cs 文件里的代码:

       public event EventHandler Tick;
protected virtual void OnTick(EventArgs e)
{
if (Tick != null)
{
Tick(this, e);
}
}
public void Start()
{
if (_bRunning)
{
return;
}
_threadWork = new Thread(new ThreadStart(ThreadWork));
_threadWork.IsBackground = true;
_threadWork.Start();
}
public void Stop()
{
_threadWork.Abort();
_threadWork = null;
}
private void ThreadWork()
{
while (true)
{
OnTick(new EventArgs());
Thread.Sleep(_interval);
}
}

 

这种写法是MSDN里常用的一种,但是那里没有牵涉到跨线程调用。在这个案例下,这种做法就行不通了。调试时会报错。如图。


 

分析:

这里的 OnTick 事件是在Start()方法中创建的线程中调用的,在 OnTick 中调用了 Tick,由委托机制可知,等同于调用了 Form1.cs 中的 mytimer_Tick ,而mytimer_Tick 调用了 UI 上的东西,这时在 mytimer_Tick 中就会出现跨线程调用。

 

解决办法:

改写 OnTick 方法。使用 Invoke 机制来完成调用。

protected virtual void OnTick(EventArgs e)
{
if (Tick != null)
{
if (Tick.Target is System.ComponentModel.ISynchronizeInvoke)
{
System.ComponentModel.ISynchronizeInvoke aSynch = Tick.Target as System.ComponentModel.ISynchronizeInvoke;
if (aSynch.InvokeRequired)
{
object[] args = new object[2] {this, e };
aSynch.BeginInvoke(Tick, args);
}
else
{
Tick(this, e);
}
}
}
}

 

参考:http://www.wxzzz.com/?id=76