今天有时间了,继续《编写高质量代码改善程序的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     }

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

编写高质量代码改善程序的157个建议:第87个建议之区分WPF和WinForm的线程模型...相关推荐

  1. 编写高质量代码改善C#程序的157个建议——建议148:不重复代码

    建议148:不重复代码 如果发现重复的代码,则意味着我们需要整顿一下,在继续前进. 重复的代码让我们的软件行为不一致.举例来说,如果存在两处相同的加密代码.结果在某一天,我们发现加密代码有个小Bug, ...

  2. 编写高质量代码改善C#程序的157个建议——建议86:Parallel中的异常处理

    建议86:Parallel中的异常处理 建议85阐述了如何处理Task中的异常.由于Task的Start方法是异步启动的,所以我们需要额外的技术来完成异常处理.Parallel相对来说就要简单很多,因 ...

  3. 编写高质量代码改善C#程序的157个建议——建议87:区分WPF和WinForm的线程模型...

    建议87:区分WPF和WinForm的线程模型 WPF和WinForm窗体应用程序都有一个要求,那就是UI元素(如Button.TextBox等)必须由创建它的那个线程进行更新.WinForm在这方面 ...

  4. 编写高质量代码改善C#程序的157个建议——建议127:用形容词组给接口命名

    建议127:用形容词组给接口命名 接口规范的是"Can do",也就是说,它规范的是类型可以具有哪些行为.所以,接口的命名应该是一个形容词,如: IDisposable表示可以被释 ...

  5. 编写高质量代码改善C#程序的157个建议——建议133:用camelCasing命名私有字段和局部变量...

    建议133:用camelCasing命名私有字段和局部变量 私有变量和局部变量只对本类型负责,它们在命名方式也采用和开放的属性及字段不同的方法.camelCasing很适合这类命名. camelCas ...

  6. 编写高质量代码改善C#程序的157个建议——建议104:用多态代替条件语句

    建议104:用多态代替条件语句 假设要开发一个自动驾驶系统.在设计之初,此自动驾驶系统拥有一个驾驶系统命令的枚举类型: enum DriveCommand{Start,Stop} 当前该枚举存在两个命 ...

  7. 编写高质量代码改善C#程序的157个建议——建议157:从写第一个界面开始,就进行自动化测试...

    建议157:从写第一个界面开始,就进行自动化测试 如果说单元测试是白盒测试,那么自动化测试就是黑盒测试.黑盒测试要求捕捉界面上的控件句柄,并对其进行编码,以达到模拟人工操作的目的.具体的自动化测试请学 ...

  8. 编写高质量代码改善C#程序的157个建议——建议130:以复数命名枚举类型,以单数命名枚举元素...

    建议130:以复数命名枚举类型,以单数命名枚举元素 枚举类型应该具有负数形式,它表达的是将一组相关元素组合起来的语义.比如: enum Week{Monday,Tuesday,Wednesday,Th ...

  9. 编写高质量代码改善C#程序的157个建议——建议50:在Dispose模式中应区别对待托管资源和非托管资源...

    建议50:在Dispose模式中应区别对待托管资源和非托管资源 真正资源释放代码的那个虚方法是带一个bool参数的,带这个参数,是因为我们在资源释放时要区别对待托管资源和非托管资源. 提供给调用者调用 ...

最新文章

  1. html页面在微信分享php,详解html静态页面实现微信分享思路的示例代码分析
  2. Tomcat的安装和配置及出错处理
  3. Linux系统管理技术手册——第6章 添加新用户
  4. Codeforces Round #656 (Div. 3) F. Removing Leaves 贪心 + 模拟
  5. Mybatis源码研究5:数据源的实现
  6. 5月7日MySQL 学习
  7. MediaElementAudioSourceNode
  8. 《C程序员从校园到职场》一2.2 破除错误观念
  9. java中String类和StringBuffer类实例详解
  10. docker-compose文件详解
  11. php是什么电器元件,常见电子元件识别
  12. 第一章 tooltips自定义
  13. MySQL 全局配置 --secure-file-priv
  14. 如何批量打印 带图片名字的图片?Word 宏命令
  15. 华为手机为什么打不开微信连接到服务器,华为手机微信打不开如何解决? 华为手机微信打不开解决方法介绍!...
  16. Vue.js+Layer表格数据绑定与实现更新
  17. 基于三维地图的可视化工厂优势
  18. 转行程序员日记---2020-09-18【,勿忘国耻】【回忆青春】
  19. python使用什么来表示代码块_Python入门之基本语法
  20. 地产“罗生门”:是世茂“不讲武德”,还是福晟“谎话连篇”?

热门文章

  1. 如何用VB实现Excel文件的自动合并
  2. Linux操作系统下软件的安装与卸载
  3. 在RedHat Enterprise Linux 上Oracle 9i的安装配置与调优
  4. MNIST数据集重生!测试图片增加到6万张,LeCun点赞
  5. SpaceX载人龙飞船意外爆炸,据称几乎被完全摧毁
  6. 如何入门AI?五大新手项目奉上
  7. 又一中科院AI创业公司浮出水面,刚拿下AIC挑战赛视觉感知冠军
  8. 第三百零九节,Django框架,models.py模块,数据库操作——F和Q()运算符:|或者、并且——queryset对象序列化...
  9. [转] [Elasticsearch] 数据建模 - 处理关联关系(1)
  10. android-async-http取消请求