zoukankan      html  css  js  c++  java
  • 编写高质量代码改善程序的157个建议:第87个建议之区分WPF和WinForm的线程模型

        今天有时间了,继续《编写高质量代码改善程序的157个建议》的阅读,当我阅读到建议87的时候,里面的一些代码示例和文中所说的不一致了,是不是我现在用的是NetFramework 4.0的缘故,已经把一些问题修复了,今天把问题写下来,告诉大家文中有些小问题需要修复一下。

       WPF和WinForm窗体应用程序都有一个要求,那就是UI元素(Button,Label,Textbox控件等)必须由创建它的那个线程来更新。WinForm这方面的限制并不是很严格,所以像下面这样的代码,在Winform中的大部分情况下都可以运行:

    private void buttonStartAsync_Click(object sender,EventArgs e)
    {
        Task t=new Task(()=>{
          while(true)
          {
             label1.Text=DateTime.Now.ToString();
             Thread.Sleep(1000);
           }
        });
       t.ContinueWith((task)=>{
         try
         {
            task.Wait();
         }
         catch(AggregateException ex)
         {
             Foreach(Exception item in ex.InnerExceptions)
             {
                 MessageBox.Show(string.Format("异常类型:{0}{1}来自:{2}{3}异常内容:{4}",item.GetType(),Environment.NewLine,item.Source,Environment.NewLine,item.Message));
             }
          }
        },TaskContinuationOptions.OnlyOnFauled);
       t.Start();
    }

    我把这段代码原封不动的有敲了一遍,但是在我的测试实例里面抛出了异常,截图如下:

    现在在Winform里面,我测试过的Task和多线程的操作都会报这个错误,修正这个错误很容易,可以在当前类的构造函数里面增加一下一段代码就可以:

    CheckForIllegalCrossThreadCalls = false;

    代码效果截图如下:

    现在就好了,程序就可以正常运行了,我的文章【其他信息: 线程间操作无效: 从不是创建控件“控件名”的线程访问它。】可以解决这类问题,有详细解释。

    所以说,WPF和WinForm都是严格执行主线程操作UI元素的原则。

     处理多线程情况下访问UI控件还有很多方法,现在我就在罗列出一下代码:

     1 Task t = new Task(()=> {
     2            while (true)
     3            {
     4                     if (lblResult.InvokeRequired)
     5                     {
     6                         lblResult.BeginInvoke(new Action(() =>
     7                         {
     8                             lblResult.Text = DateTime.Now.ToString();
     9                         }));
    10                     }
    11                     else
    12                     {
    13                         lblResult.Text = DateTime.Now.ToString();
    14                     }
    15                     Thread.Sleep(1000);
    16                 }
    17             });
    18             t.ContinueWith((task)=> {
    19                 try
    20                 {
    21                     task.Wait();
    22                 }
    23                 catch (AggregateException ex)
    24                 {
    25                     foreach (var item in ex.InnerExceptions)
    26                     {
    27                         MessageBox.Show(string.Format("异常类型:{0}{1}来自:{2}{3}异常内容:{4}",item.GetType(),Environment.NewLine,item.Source,Environment.NewLine,item.Message));
    28                     }
    29                 }
    30             },TaskContinuationOptions.OnlyOnFaulted);
    31             t.Start();

    我们可以模仿WPF处理线程的方法,增加两个新方法,这两个方法是CheckAccess和VerifyAccess,这两个方法是WPF的UI控件的最终积累DispatcherObject类型中的两个方法,代码如下:

     1 namespace System.Windows.Threading
     2 {
     3     //
     4     // 摘要:
     5     //     表示与 System.Windows.Threading.Dispatcher 关联的对象。
     6     public abstract class DispatcherObject
     7     {
     8         //
     9         // 摘要:
    10         //     初始化 System.Windows.Threading.DispatcherObject 类的新实例。
    11         protected DispatcherObject();
    12 
    13         //
    14         // 摘要:
    15         //     获取与此 System.Windows.Threading.DispatcherObject 关联的 System.Windows.Threading.Dispatcher。
    16         //
    17         // 返回结果:
    18         //     调度程序。
    19         [EditorBrowsable(EditorBrowsableState.Advanced)]
    20         public Dispatcher Dispatcher { get; }
    21 
    22         //
    23         // 摘要:
    24         //     确定调用线程是否可以访问此 System.Windows.Threading.DispatcherObject。
    25         //
    26         // 返回结果:
    27         //     如果调用线程可以访问此对象,则为 true;否则,为 false。
    28         [EditorBrowsable(EditorBrowsableState.Never)]
    29         public bool CheckAccess();
    30         //
    31         // 摘要:
    32         //     强制调用线程具有此 System.Windows.Threading.DispatcherObject 的访问权限。
    33         //
    34         // 异常:
    35         //   T:System.InvalidOperationException:
    36         //     调用线程不可以访问此 System.Windows.Threading.DispatcherObject。
    37         [EditorBrowsable(EditorBrowsableState.Never)]
    38         public void VerifyAccess();
    39     }
    40 }

    然后,我们给自己的类型加两个类似的方法,完整代码如下:

     1  public partial class Form1 : Form
     2     {
     3         private Thread mainThread;
     4         public Form1()
     5         {
     6             InitializeComponent();
     7         }
     8 
     9         bool CheckAccess()
    10         {
    11             return mainThread == Thread.CurrentThread;
    12         }
    13 
    14         void VerifyAccess()
    15         {
    16             if (!CheckAccess())
    17             {
    18                 throw new InvalidOperationException("调用线程无法访问对象,因为另一个线程拥有此对象!");
    19             }
    20         }
    21         private void button1_Click(object sender, EventArgs e)
    22         {
    23             Task t = new Task(()=> {
    24                 while (true)
    25                 {
    26                     if (!CheckAccess())
    27                     {
    28                         lblResult.BeginInvoke(new Action(() =>
    29                         {
    30                             lblResult.Text = DateTime.Now.ToString();
    31                         }));
    32                     }
    33                     else
    34                     {
    35                         lblResult.Text = DateTime.Now.ToString();
    36                     }
    37                     Thread.Sleep(1000);
    38                 }
    39             });
    40             t.ContinueWith((task)=> {
    41                 try
    42                 {
    43                     task.Wait();
    44                 }
    45                 catch (AggregateException ex)
    46                 {
    47                     foreach (var item in ex.InnerExceptions)
    48                     {
    49                         MessageBox.Show(string.Format("异常类型:{0}{1}来自:{2}{3}异常内容:{4}",item.GetType(),Environment.NewLine,item.Source,Environment.NewLine,item.Message));
    50                     }
    51                 }
    52             },TaskContinuationOptions.OnlyOnFaulted);
    53             t.Start();
    54         }
    55     }

    多线程是一个很复杂的话题,我也在学习阶段和总结阶段,有不足的地方,大家多多指教。

  • 相关阅读:
    【转】return 使用示例
    java基础_二维数组的行和列
    新版SQL授权用户时报错 near 'IDENTIFIED BY '密码' with grant option' at line 1
    GO kafka sarama 生产者 消费者 简单 实现
    Windows 安装kafka
    windows 连接nsq
    reflect: call of reflect.Value.NumField on ptr Value
    django 数据库 mysql 事务 处理
    python 类的继承
    python 中 insert 返回 None
  • 原文地址:https://www.cnblogs.com/PatrickLiu/p/7119860.html
Copyright © 2011-2022 走看看