在Silverlight中引入多线程的根本动机主要是为了解决用户体验中的响应速度,进而减少单线程带来的阻塞问题。
把我自己从测试以及其他人的文章中得出的理解分享一下,有不对的地方也请指正:
第一条是,Silverlight只有一个主线程,也就是我们常说的UI线程。
第二条是这个主线程实际上负责很多的事情,例如,处理用户输入,呈现页面,运行动画等等。
下面先来写一个小例子,说明一下多线程是如何减少界面假死的。
新建一个Sliverlight项目,在MianPage上面添加一个按钮和TextBlock,按钮用来触发事件,调用方法。
接下来写一个函数,代码如下:
public Int64 UpdateText() { Int64 r = 0; for (int i = 0; i <= 999999999; i++) { r += i; } return r; }
这个函数执行起来是很耗时间的,如果电脑性能差,就会更慢。
首先,写一般的调用方法:
private void button1_Click(object sender, RoutedEventArgs e) { this.textBlock1.Text = "Result:" + UpdateText(); }
调试运行,点击Button按钮,发现按钮立刻变为不可用状态,过段时间,计算完成,按钮才变为可用状态。
下面来说一些多线程的方法,大家都知道.NET 中,是不允许跨线程访问的,在Silverlight中可以使用Dispatcher来刷新UI界面。
private void button1_Click(object sender, RoutedEventArgs e) { new Thread(() => { Int64 r = 0; for (int i = 0; i <= 999999999; i++) { r += i; } this.Dispatcher.BeginInvoke(() => { this.textBlock1.Text = "Current:" + r; }); }).Start(); }
这时候运行,就会发现按钮不会变为不可用的状态了。注意函数实在线程中执行的,但更新UI调用的是this.Dispatcher.BeginInvoke()方法。
不要把调用的方法写到this.Dispatcher.BeginInvoke()里面,否则界面还是会假死。
好了,下面开始说一下Sliverlight中的五中多线程机制:
1.使用Thread类
Thread类是在Silverlight中你首先应该了解的多线程编程工具。在Thread类中定义了许多成员,相信大家在学习winform时已经了解了。
public partial class ThreadTestPage : Page { string result = ""; public ThreadTestPage() { InitializeComponent(); ThreadTestMethod(); } private void ThreadTestMethod() { System.Threading.Thread thread = new System.Threading.Thread(DoWork); thread.Name = "ThreadDemoOne"; thread.IsBackground = true; thread.Start(1000); result += thread.IsAlive + "\r\n"; result += thread.ManagedThreadId + "\r\n"; result += thread.Name + "\r\n"; result += thread.ThreadState + "\r\n"; if (thread.Join(5000)) { result += "The specified thread has terminated within 5 seconds.\r\n"; }
txtMsg.Text = result;
}
void DoWork(object sleepMillisecond)
{
System.Threading.Thread.Sleep((int)sleepMillisecond);
result += "The thread terminated!\r\n";
}
第一,我们应该首先创建一个新的Thread对象。在上面的例子中,我们提供一个委托来指向要异步调用的方法。在这种情况下,DoWork是由一个后台线程(这里的委托类型省略)执行的方法。注意,ThreadStart委托不能带参数,而ParameterizedThreadStart委托可以带参数。后面的例子将展示相关的使用。
第二,IsBackground属性指示这是否是一个后台线程(注意,在Silverlight中并没有区分是否是一个后台线程)。接下来,Start方法用于启动线程,传递一个整数来指定睡眠时间(毫秒)。请注意,Start方法立即返回,并且相关的代码开始在新线程上异步执行。事实上,我们甚至可以将任何对象传递给Start的方法。
还要注意的是,另外一个方法Join是用来阻止调用者线程(在上面的情况下,即指主线程),直到指定的线程(在上面的情况下,即指线程thread)已完成。如果指定的线程完成,则继续执行后面的语句;如果指定的线程运行比指定的时间长,还要继续进行。返回值的意义在于指定,在指定的时间内,是否完成指定的线程执行。
2.使用System.Windows.Threading.Dispatcher
我们写的第一个例子用的就是这种方法,现在来具体说一下。
void DoWork(object sleepMillisecond) { this.Dispatcher.BeginInvoke((ThreadStart) delegate() { button1.Content = "Some text!"; }); }
这样写的话,就可以在线程中修改控件的内容了。如果不使用Dispatcher的话,就会出现一个“无效的跨线程访问”异常。
当然还可以写成这样的形式:
void DoWork(object sleepMillisecond) { this.Dispatcher.BeginInvoke(()=> { button1.Content = "Some text!"; }); }
3.使用Deployment.Current.Dispatcher
截至目前,上面提供的示例都是在UI控件已经启动的前提下进行的。正如我们所知道的,一般情况下Application.Current.RootVisual.Dispatcher属性引入的目的主要用于检索一个应用程序的System.Windows.Threading.Dispatcher。但是,如果在RootVisual创建之前这种操作是不会得到支持的。为了在创建RootVisual之前获得应用程序的一个调度器Dispatcher,我们可以借助于System.Windows.Deployment.Current.Dispatcher对象。
还有另外一个情况是,在.dll程序集情况下,我们也可以通过使用Deployment.Current.Dispatcher来获得应用程序的调度器Dispatcher的一个引用。例如,要改变一个UI线程中的Silverlight控件的Text属性值,你可以使用下面的代码:
private void Button_Click(object sender, RoutedEventArgs e) { new Thread(() => { Deployment.Current.Dispatcher.BeginInvoke(() => { this.TextBlock1.Text = DateTime.Now.ToString(); }); }).Start(); }
注意,创建完线程,一定要Start()才会执行。
4.使用SynchronizationContext
相比于以前的对象,SynchronizationContext似乎有点神秘。根据MSDN的介绍,SynchronizationContext对象能够提供各种同步模型环境下的传播同步的上下文的基本功能。据我从网上搜索的结果,结论应该是:对SynchronizationContext的发明旨在简化同步—只要你确保你是在UI线程内;否则,它会返回一个空值。因此,在大多数情况下,你可以使用Deployment.Current.Dispatcher来作为System.Windows.Deployment.Current.Dispatcher的替代。至于使用SynchronizationContext,并不是一件困难的事情。例如,你也可以如下所示来同步实现与上述类似的操作。
private void Button_Click(object sender, RoutedEventArgs e) { var context = SynchronizationContext.Current; new Thread(() => { context.Send((s) => { this.TextBlock1.Text = DateTime.Now.ToString(); }, null); }).Start(); }
5.使用线程池
在所有的多线程解决方案中,ThreadPool应该是你最常用的技术。使用线程池的好处是明显的:
1、它是易于控制的,而且功能也很强大,有助于降低多线程编程的整体代价;
2、在线程池中一个线程不会由于任务的结束而灭绝,而是将继续执行其他任务,从而可以大大减少线程创建和销毁的开销。线程池提供了一个重要方法—QueueUserWorkItem。借助于此方法,能够把任何任务推入一个后台线程中,然后在后台队列中执行相应的功能。同时,它的创建也相当简单。
private void Button_Click(object sender, RoutedEventArgs e) { ThreadPool.QueueUserWorkItem((s) => { this.Dispatcher.BeginInvoke(() => { int minWorkerThreads, minCompletionPortThreads, maxWorkerThreads, maxCompletionPortThreads; ThreadPool.GetMinThreads(out minWorkerThreads, out minCompletionPortThreads); ThreadPool.GetMaxThreads(out maxWorkerThreads, out maxCompletionPortThreads); this.TextBox1.Text = String.Format("WorkerThreads = {0} ~ {1}, CompletionPortThreads = {2} ~ {3}", minWorkerThreads, maxWorkerThreads, minCompletionPortThreads, maxCompletionPortThreads); }); }); }
先写到这里了,如果有什么不明白的,可以留言,一起交流学习。