Silverlight 4中五种多线程编程技巧

Silverlight 4中五种多线程编程技巧,第1张

概述在本系列文章中,我想尽可能详细地总结Silverlight 4对于多线程编程技术的支持。其中提供的相关示例代码供读者朋友参考,并欢迎到我的博客(http://space.itpub.net/14466241/)处一起作更深入的探讨。   1.使用Thread类   Thread类是在Silverlight中你首先应该了解的多线程编程工具。在Thread类中定义了许多成员。因为这个类也是C#编程的第 在本系列文章中,我想尽可能详细地总结Silverlight 4对于多线程编程技术的支持。其中提供的相关示例代码供读者朋友参考,并欢迎到我的博客(http://space.itpub.net/14466241/)处一起作更深入的探讨。

  1.使用Thread类

  Thread类是在Silverlight中你首先应该了解的多线程编程工具。在Thread类中定义了许多成员。因为这个类也是C#编程的第一个必须;所以,在此我们不一一列举,而仅用一个具体的例子,说明这个类的基本用法。

  清单1:

       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
= " ThreadDemo " ;

  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类,要几点需要注意:

  第一,我们应该首先创建一个新的Thread对象。在上面的例子中,我们提供一个委托来指向要异步调用的方法。在这种情况下,DoWork是由一个后台线程(这里的委托类型省略)执行的方法。注意,ThreadStart委托不能带参数,而ParameterizedThreadStart委托可以带参数。后面的例子将展示相关的使用。

  第二,IsBackground属性指示这是否是一个后台线程(注意,在Silverlight中并没有区分是否是一个后台线程)。接下来,Start方法用于启动线程,传递一个整数来指定睡眠时间(毫秒)。请注意,Start方法立即返回,并且相关的代码开始在新线程上异步执行。事实上,我们甚至可以将任何对象传递给Start的方法。

  有关Start方法,请参考以下定义:

  清单2:

public voID Start(

  
Object parameter

  );

public voID Start(

  
Object parameter

  );

  还要注意的是,另外一个方法Join是用来阻止调用者线程(在上面的情况下,即指主线程),直到指定的线程(在上面的情况下,即指线程thread)已完成。如果指定的线程完成,则继续执行后面的语句;如果指定的线程运行比指定的时间长,还要继续进行。返回值的意义在于指定,在指定的时间内,是否完成指定的线程执行。

  现在,让我们来思考下面的十分有趣的事情:

  清单3:

voID DoWork( object sleepMillisecond)

  {

  button1.Content
= " Hello World! " ;

  
// 省略其他内容……

  }

  执行上面的代码将会失败—系统将会抛出一个运行时刻UnauthorizedAccessException警告“无效的跨线程访问”。这表明,系统不允许线程访问Silverlight对象。这其实提出了一个多线程环境下的典型问题。为了解决这个问题,通常建议借助于使用System.windows.Threading.dispatcher对象。下一节正要探讨这个对象。

  2.使用System.windows.Threading.dispatcher

  在正式讨论System.windows.Threading.dispatcher对象之前,让我们先看看如何解决上述问题。


  清单4:

       voID DoWork( object sleepMillisecond)

  {

  dispatcher.BeginInvoke((ThreadStart) delegate()

  {

  button1.Content
= " Hello World! " ;

  });

  
// 省略其他内容……

  }

  再次运行上面的代码,你会发现button控件的Content属性值已修改成功。要获得一个更加模块化的设计风格,也可以如下表达相同的功能:

  清单5:

voID DoWork( object sleepMillisecond)

  {

  dispatcher.BeginInvoke(ChangeIt);

  
// 省略其他内容……

  }

  
private voID ChangeIt()

  {

  button1.Content
= " Hello World! " ;

  }

  正如你在其他许多的框架中所看到的,Silverlight线程也分为两类:用户界面线程和工作者线程。Silverlight的UI线程是与用户进行交互的线程。在UI线程中,专门设计了一些用户界面控件类和视图模型(viewmodel)类用于实现数据绑定。根据Silverlight设计框架的规定,后台工作线程不能直接访问UI线程的数据对象和控件中的属性。但是,你也没有必要为此过于担心。Silverlight中的线程模型中已经提供了一个安全的基于事件的调度器(dispatcher)模型,它类似于Java Swing中的EDT(事件调度线程)。借助于这种事件调度器机制,我们也可以轻松完成UI线程和后台工作线程之间的数据交互。

  我们知道,所有Silverlight控件都继承于DependencyObject这个基类。值得注意是,DependencyObject类不仅为Silverlight提供了基本的依赖性服务,也开启了一条UI线程和后台工作线程之间的数据交互的通道。DependencyObject具有一个非常重要的属性-dispatcher。因此,后台线程可以调用发射器(主要是UI控件)的dispatcher对象来实现上述数据交互之目的。下面举例说明一个使用这种机制的典型的 *** 作模式:

  清单6:

_UISender.dispatcher.BeginInvoke(() =>

  {

  
// 我们可以在此访问UI线程中的对象,因为代理本身是在UI线程的上下文中执行的

  }

  上述()=>是一个lambda表达式,这是一种没有传入参数的委托方法的缩写形式。如果有传入的参数的话,我们可以将其在括号中指定。

  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属性值,你可以使用下面的代码:

  清单7:

       private voID button_Click( object sender,RoutedEventArgs e)

  {

  
new Thread(() =>

  {

  Deployment.Current.dispatcher.BeginInvoke(()
=>

  {

  this.TextBlock1.Text
= DateTime.Now.ToString();

  });

  }).Start();

  }

  顺便说一句,网址http://blogs.infragistics.com/blogs/mihail_mateev/archive/2010/04/18/build-facebook-applications-with-silverlight-3-and-silverlight-4-part-4.aspx处提供了一个很好的例子,供大家参考之用。

  4.使用SynchronizationContext

  相比于以前的对象,SynchronizationContext似乎有点神秘。根据MSDN的介绍,SynchronizationContext对象能够提供各种同步模型环境下的传播同步的上下文的基本功能。据我从网上搜索的结果,结论应该是:对SynchronizationContext的发明旨在简化同步—只要你确保你是在UI线程内;否则,它会返回一个空值。因此,在大多数情况下,你可以使用Deployment.Current.dispatcher来作为System.windows.Deployment.Current.dispatcher的替代。至于使用SynchronizationContext,并不是一件困难的事情。例如,你也可以如下所示来同步实现与上述类似的 *** 作。

  清单8:

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();

  }

         注意,丹尼尔.沃恩(http://www.codeproject.com/Articles/51457/Synchronous-Invocation-of-Delegates-with-the-Silve.aspx)为SynchronizationContext提供了一个良好的封装类;有基础的读者可以对这个SynchronizationContext对象进行更深入的探讨。


        5.使用线程池

  在所有的多线程解决方案中,ThreadPool应该是你最常用的技术。使用线程池的好处是明显的:1,它是易于控制的,而且功能也很强大,有助于降低多线程编程的整体代价;2,在线程池中一个线程不会由于任务的结束而灭绝,而是将继续执行其他任务,从而可以大大减少线程创建和销毁的开销。线程池提供了一个重要方法—QueueUserWorkItem。借助于此方法,能够把任何任务推入一个后台线程中,然后在后台队列中执行相应的功能。同时,它的创建也相当简单。

  事实上,你会发现在执行相同任务的情况下,与上述方法并不存在太大的差别。

  清单9:

        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,maxCompletionPortThreads);

  });

  });

  }

  下面,让我们更仔细地探讨一下线程池的使用。在下面的代码中,我们要创建两个示例:一是仍然涉及到方法QueueUserWorkItem,另一个涉及到方法RegisterWaitForSingleObject。

  首先,让我们把两个TextBlock控件放在ThreadPoolTestPage.xaml示例页面上。如下所示:

  清单10:

< StackPanel HorizontalAlignment = " left " margin = " 5,5,5 "   GrID.Row = " 1 " WIDth = " 623 " >

    
< TextBlock x:name = " txtMsgQueueUserWorkItem " Text = " Click here to start thread 1 "

MouseleftbuttonDown
= " txtMsgQueueUserWorkItem_MouseleftbuttonDown " margin = " 30 " />

    
< TextBlock x:name = " txtRegisterWaitForSingleObject " Text = " Click here  to start thread 2 "

MouseleftbuttonDown
= " txtRegisterWaitForSingleObject_MouseleftbuttonDown " margin = " 30 " />

</ StackPanel >

  当点击上述任意两个标签之一,相应的程序过程将被启动。下面,让我们继续跟踪观察在后台代码中发生的情况:

  清单11:

// 省略其他内容……

  using System.Threading;

  namespace SilverlightmultiThread

  {

  
public partial class ThreadPoolTestPage : Page

  {

      public ThreadPoolTestPage()

  {

  InitializeComponent();

  }

  
private voID txtMsgQueueUserWorkItem_MouseleftbuttonDown( object sender,MousebuttonEventArgs e)

  {

  System.Threading.ThreadPool.QueueUserWorkItem(DoWork,DateTime.Now);

  }

private voID DoWork( object state)

  {

  DateTime dtJoin
= (DateTime)state;

  DateTime dtStart
= DateTime.Now;

  System.Threading.Thread.Sleep(
3000 );

  DateTime dtEnd
= DateTime.Now;

  this.dispatcher.BeginInvoke(()
=>

  {

  txtMsgQueueUserWorkItem.Text
+= string .Format( " \r\nInto-quene time: {0}start time: {1}end time: {2} " ,

  dtJoin.ToString(),dtStart.ToString(),dtEnd.ToString());

  });

  }

  
private voID txtRegisterWaitForSingleObject_MouseleftbuttonDown( object sender,MousebuttonEventArgs e)

  {

  System.Threading.autoresetEvent done
= new System.Threading.autoresetEvent( false );

  RegistereDWaitHandlePacket packet
= new RegistereDWaitHandlePacket();

  packet.Handle
= System.Threading.ThreadPool.RegisterWaitForSingleObject

  (

  done,

  WaitOrTimer,

  packet,

  
100 ,

  
false

  );


       System.Threading.Thread.Sleep( 555 );

  done.Set();
// 发送一个信息来调用由RegisterWaitForSingleObject指定的方法

  }

  
public voID WaitOrTimer( object state,bool timedOut)

  {

  RegistereDWaitHandlePacket packet
= state as RegistereDWaitHandlePacket;

  
// bool timedOut –指示是否由于超时而执行到此

  
if (!timedOut)

  {

  
// 如果没有由于超时而执行到此,则取消指定的RegistereDWaitHandle

  packet.Handle.Unregister(
null );

  }

  this.dispatcher.BeginInvoke(()
=>

  {

  txtRegisterWaitForSingleObject.Text
+=

  
String .Format( " \r\n是否收到信号: {0} " ,(!timedOut).ToString());

  });

  }

  }

  
/// 用于封装RegistereDWaitHandle类

  
public class RegistereDWaitHandlePacket

  {

  
public System.Threading.RegistereDWaitHandle Handle { get ; set ; }

  }

  }

  对于第一种方法QueueUserWorkItem,我们只要注意两点。首先是参数的定义:

  列表12:QueueUserWorkItem方法存在两个重载

QueueUserWorkItem(WaitCallback);

QueueUserWorkItem(WaitCallback,
Object );

  在这里,第一个参数用于指定要执行的方法,而第二个参数作为参数传递进方法中。

  其次,仅当在线程池中的线程变为可用时才执行上面的方法。如你所想象的,作为线程管理策略的一部分,在线程池创建线程之前总会存在一定程度的延迟。这就是为什么我们调用Thread.Sleep方法的原因。

  对于第二种方法RegisterWaitForSingleObject,倒有一些复杂。首先,我们创建了一个autoresetEvent的实例,传递进一个false参数。然后,我们引进一个辅助类RegistereDWaitHandlePacket来封装另一个RegistereDWaitHandle类。注意,RegisterWaitForSingleObject方法中可以使用多个参数。不过,我们不会再过细地介绍,因为MSDN(http://msdn.microsoft.com/en-us/library/system.threading.threadpool.aspx)已作出更详细的解释。此外,你也会注意到我们对第二种方法采用了延迟策略。

  最后值得注意的是,虽然ThreadPool还提供了两个方法SetMinThreads和SetMaxThreads,但是,在Silverlight中是不可用的,调用它们将会引发异常。有兴趣的读者可以自行试验。

  6.小结

  在本文中,我们介绍了Silverlight 4编程环境下的五种多线程编程。

总结

以上是内存溢出为你收集整理的Silverlight 4中五种多线程编程技巧全部内容,希望文章能够帮你解决Silverlight 4中五种多线程编程技巧所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/1019311.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-23
下一篇 2022-05-23

发表评论

登录后才能评论

评论列表(0条)

保存