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

时间:2022-12-01 22:35:08

[知识背景]

所谓自定义通知事件,就是指在自己的类中定义的事件,该事件用于向调用者发出通知。比如做一个下载工具,下载是需要时间的,用户在界面里点击“下载”之后,我的下载类在后台开启线程开始传输数据,前台界面上可以同时执行其他操作。当数据传输完成,需要通知界面(调用者)已完成下载,以便界面上做相应的改变。这就需要在我的下载类中有类似 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 里的代码:

 1         public Form1()
 2         {
 3             InitializeComponent();
 4             MyTimer mytimer = new MyTimer();
 5             mytimer.Tick += new EventHandler(mytimer_Tick);
 6             mytimer.Start();
 7         }
 8 
 9         void mytimer_Tick(object sender, EventArgs e)
10         {
11 
12             CheckBox check = new CheckBox();
13             check.Top = this.Controls.Count * check.Height;
14             this.Controls.Add(check);
15         }

 

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 机制来完成调用。

 

  1        protected virtual void OnTick(EventArgs e)

 2          {
 3               if  (Tick  !=   null )
 4              {
 5                   if  (Tick.Target  is  System.ComponentModel.ISynchronizeInvoke)
 6                  {
 7                      System.ComponentModel.ISynchronizeInvoke aSynch  =  Tick.Target  as  System.ComponentModel.ISynchronizeInvoke;
 8                       if  (aSynch.InvokeRequired)
 9                      {
10                           object [] args  =   new   object [ 2 ] {this, e  };
11                          aSynch.BeginInvoke(Tick, args);
12                      }
13                       else
14                      {
15                          Tick( this , e);
16                      }
17                  }
18              }
19          }

 

 

 源代码下载:测试自定义通知事件的跨线程问题.rar