今天看书上写的一个例子,
1 private void AddMessage(string formatString, 2 params string[] parameters) 3 { 4 Dispatcher.BeginInvoke(new Action( 5 () => WatchOutput.Items.Insert( 6 0, string.Format(formatString, parameters)))); 7 }
Dispatcher.BeginInvoke是啥玩意,为啥要这么做呢?为啥不直接用
1 WatchOutput.Items.Insert(0, string.Format(formatString, parameters));
于是就做了个实验,当用下面这个语句的时候,点击Watch按钮的时候,没有问题,但当我修改对应文件的属性时,触发事件,此时报错。
未处理InvalidOperationException“由于其他线程拥有此对象,因此调用线程无法对其进行访问。”
上网查了下,原来是线程问题,WPF应用程序一般有两个线程,一个用于处理呈现(渲染UI),一个用来管理UI。呈现线程有效地隐藏在后台进行,而UI线程则接受输入、处理事件、绘制屏幕以及运行应用程序代码。
那为啥上面会出现这个问题呢?点击watch,没有问题,但当修改文件属性等操作时,就会出问题。这是因为有些事件处理函数,当事件被触发时,代码执行到事件处理函数里面的时候,执行代码的线程往往不是主线程,而出于对UI控件的保护,由UI进程创建的控件,其它线程是不能访问UI线程里的东西的。
通过下面能看到,执行事件时,是另外一个线程。
1)当点击Watch!按钮,触发click事件,此时的线程是主线程(UI线程)
2)当修改文件属性,触发created\changed等事件时,会创建一个新的工作线程。
注意:这里并不是所有的事件处理函数都会创建新的线程,从上面的例子就能看出来。
那既然只有主线程可以修改UI ,其它线程应该如何与用户交互呢?其它线程可以请求UI线程代表他执行操作,也就是通过向dispatcher中注册工作项来完成,dispatcher提供两个注册工作项的方法:invoke(同步)和Begininvoke(异步)。
从上面的例子看到,为了能向界面中添加文本,向dispather中注册了事件(采用lamda表达式)。
在我们用的系统中,也用到了Invoke:在显示next business时。在主线程中通过ParameterizedThreadStart创建一个新的进程。在这个进程中要修改界面上的内容,就只能调用invoke方法。