今天有时间了,继续《编写高质量代码改善程序的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 }
多线程是一个很复杂的话题,我也在学习阶段和总结阶段,有不足的地方,大家多多指教。