• 学习之路五:再议自定义时钟类(跨线程间的访问操作) → 异步操作


       还记得两个月之前写了一篇通过线程来自定义时钟类,当时以为这是什么高深的技术呢,其实这其中的原理就是异步六种模式中一种!

      链接如下:学习之路三:关于运用单线程和委托以及事件自定义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  //这篇推荐  

  • 相关阅读:
    当在服务器或者云端发布程序的时候,注意事项
    Oracle 在使用pivot时,注意事项
    当采用datagrid自带的方法加载列表时,当在后台遇到错误时,如何在前台提示错误
    Oracle 在分组的同时,取每个分组的前几条PARTITION BY
    带有res资源文件的项目 需要导成jar包 供别人使用的解决方法
    java乱码问题
    网络通信框架Volley使用详细说明
    深刻的理解Fragment生命周期 都在做什么,fragment生命周期
    ScrollView中嵌套ListView时,listview高度显示的问题
    关于一个app中数据库的问题
  • 原文地址:https://www.cnblogs.com/yangcaogui/p/2350870.html
Copyright © 2020-2023  润新知