学习之路五:再议自定义时钟类(跨线程间的访问操作) → 异步操作
还记得两个月之前写了一篇通过线程来自定义时钟类,当时以为这是什么高深的技术呢,其实这其中的原理就是异步六种模式中一种!
链接如下:学习之路三:关于运用单线程和委托以及事件自定义Timer类
几天前偶然打开这篇文章看了看,有位兄弟说使用异步实现会比较好,于是就用了异步方式重构自己的时钟类 !
在这期间遇到了一个问题:就是跨线程间的访问操作!
1.时钟代码如下:
1 /*
2 知识点:
3 * 1.异步委托 → 相对于创建后台线程,异步委托优势比较明显
4 * 2.异步和事件的综合运用 → 按照规范来命名
5 */
6
7 public class MyTimerForAsync
8 {
9 //定义一些私有变量
10 private EventHandler<MyTimerEventArgs> _customerHandler; //定义委托变量,使用.NET Framework自己定义的委托类型
11 private int _distance; //间隔时间
12 private bool _flag = true; //启动时钟的标志
13
14 //定义事件
15 public event EventHandler<MyTimerEventArgs> Tick;
16
17 public MyTimerForAsync()
18 : this(1000)
19 { }
20
21 public MyTimerForAsync(int distanceTime)
22 {
23 this._distance = distanceTime;
24 ExecuteTimer();
25 }
26
27 //定义触发事件
28 public void OnTimer(object sender, MyTimerEventArgs timer)
29 {
30 try
31 {
32 while (_flag)
33 {
34 Thread.Sleep(this._distance);
35 EventHandler<MyTimerEventArgs> handler = Tick;
36 if (handler != null)
37 {
38 handler(this, timer);
39 }
40 }
41 if (this._flag == false)
42 {
43 this._customerHandler -= OnTimer; //撤销事件
44 }
45 }
46 catch (Exception ex)
47 {
48 throw ex;
49 }
50 }
51
52 //执行异步,执行函数
53 public void ExecuteTimer()
54 {
55 this._customerHandler = new EventHandler<MyTimerEventArgs>(OnTimer);
56 //开始异步调用,执行函数和回调函数
57 this._customerHandler.BeginInvoke(this, null, CallBackTimer, null);
58 }
59
60 //回调用函数作用:①获取返回值 ②结束异步操作
61 public void CallBackTimer(IAsyncResult ar)
62 {
63 if (_flag == false)
64 {
65 AsyncResult async = ar as AsyncResult;
66 EventHandler<MyTimerEventArgs> handler = async.AsyncDelegate as EventHandler<MyTimerEventArgs>;
67 handler.EndInvoke(ar);
68 }
69 }
70
71 #region 属性
72
73 //时钟的间隔时间
74 public int DistanceTime
75 {
76 get { return this._distance; }
77 set { this._distance = value; }
78 }
79
80 #endregion
81
82 #region 打开和停止异步操作
83
84 public void Start()
85 {
86 this._flag = true;
87 ExecuteTimer();
88 }
89
90 public void Stop()
91 {
92 this._flag = false;
93 }
94
95 #endregion
96 }
97
98 //创建实体类,还可以进异步完善,添加自己的信息
99 public class MyTimerEventArgs : EventArgs
100 {
101 public MyTimerEventArgs()
102 { }
103 }
客户端调用:
1 private MyTimerForAsync _timer = new MyTimerForAsync(1000); //实例化时钟类
4 public FrmShowTimer()
5 {
6 //Control.CheckForIllegalCrossThreadCalls = false;
7 InitializeComponent();
8 this.txtDisplayTime.ReadOnly = true;
10 this._timer.Tick += new EventHandler<MyTimerEventArgs>(timer_Tick); //异步时钟,注册事件方法
17 }
34
35 //停止异步
36 void btnStopAsync_Click(object sender, EventArgs e)
37 {
38 _timer.Stop();
39 this.btnStopAsync.Enabled = false;
40 this.btnStartForAsync.Enabled = true;
41 }
42
43 //开始异步
44 void btnStartForAsync_Click(object sender, EventArgs e)
45 {
46 _timer.Start();
47 this.btnStopAsync.Enabled = true;
48 this.btnStartForAsync.Enabled = false;
49 }
68 //采用异步执行
69 void timer_Tick(object sender, MyTimerEventArgs e) //注册的事件方法
70 {
71 //this.txtDisplayTime.Text = DateTime.Now.ToString(); //一开始是这样做的
72 //this.Invoke(new Action(() => this.txtDisplayTime.Text = DateTime.Now.ToString()));
73 this.txtDisplayTime.Invoke(new Action(() => this.txtDisplayTime.Text = DateTime.Now.ToString())); //这是改进后的
//如果采用的是控件的“Invoke”方法,匿名方法中必须要有对此控件进行操作!
//不然就应该调用本窗体的“Invoke”方法 → this.Invoke
}
2.解决跨线程访问错误问题
发生问题的原因:当你在一个窗体之外创建了一个新线程的时候,如果用这个线程去访问,或者控制窗体内部控件的时候,并且当你在DEBUG的时候才会出现“不允许跨线程访问操作”的问题!
这种情况主要是发生在调试阶段,这是编译器为了线程安全而设置的一套规则,所以要尽量去避免她!
①第一种解决办法
添加如下代码: Control.CheckForIllegalCrossThreadCalls = false;
这个代码意思是禁止编译器对跨线程访问做检查,这样就能忽视这种错误,但是不保险,可能今天对了明天就错了!
②第二种解决办法
★小方法一:调用控件的“Invoke”或“BeginInvoke”方法,方法中的参数类型为Delegate,所以说里面可以传送任意的委托,不管参数多少,以及返回值!
如:
1 void timer_Tick(object sender, MyTimerEventArgs e)
2 {
3 //this.txtDisplayTime.Text = DateTime.Now.ToString();
4 //this.Invoke(new Action(() => this.txtDisplayTime.Text = DateTime.Now.ToString()));
5 this.txtDisplayTime.Invoke(new Action(() => this.txtDisplayTime.Text = DateTime.Now.ToString()));
6 this.txtDisplayTime.Invoke(new Action<string, string>(My)); //所以说可以送任何一个委托变量,只要最终继承于Delegate这个基类!
7 }
8
9 public void My(string a, string b)
10 {
11
12 }
★小方法二:使用整个窗体本身来调用
使用this.Invoke()或者“BegindInvoke()”方法来调用你要执行的方法!
说实话使用BeginInvoke就是使用了异步操作,一般情况下会配合EndInvoke一起使用!
总结:总之,使用线程的方法有点缺陷,为了代码的健壮性,需要考虑周全,如果本人写的有问题,还希望大家多给点给点建议给我这个新手!
参考文献:http://www.cnblogs.com/wangiqngpei557/archive/2011/08/24/2152096.html //这篇也推荐
http://blog.csdn.net/baple/article/details/4468891
http://www.cnblogs.com/fish-li/archive/2011/11/20/2256385.html //这篇推荐