首页 > 代码库 > 利刃 MVVMLight 8:DispatchHelper在多线程和调度中的使用

利刃 MVVMLight 8:DispatchHelper在多线程和调度中的使用

     在应用程序中,线程可以被看做是应用程序的一个较小的执行单位。每个应用程序都至少拥有一个线程,我们称为主线程,这是在启动时调用应用程序的主方法时由操作系统分配启动的线程。

     当调用和操作主线程的时候,该操作将动作添加到一个队列中。每个操作均按照将它们添加到队列中的顺序连续执行,但是可以通过为这些动作指定优先级来影响执行顺序,而负责管理此队列的对象称之为线程调度程序。

在很多情况下,我们启动新的线程主目的是执行操作(或等待某个操作的结果),而不会导致应用程序的其余部分被阻塞。密集型计算操作、高并发I/O操作等都是这种情况,所以现在的复杂应用程序日益多线程化了。

     当我们启动一个应用程序并创建对象时,就会调用构造函数方法所在的线程,对于 UI 元素,在加载 XAML 文档时,XAML 分析器会创建基于这些UI元素的对象。所以所有的对象(包括UI元素)的创建都归属于当前的主线程,当然也只有主线程可以访问他们。

但在实际情况中,有很多情况是要假手其他线程来处理的。

比如在一个长交互中,我们可能需要而外的线程来处理复杂的执行过程,以免造成线程阻塞,给用户界面卡死的错觉。

 技术分享

 

比如下面这个例子,我们使用委托的方式模拟用户执行数据创建的操作:

调用CreateUserInfoHelper帮助类 和 执行 CreateProcess方法 的代码如下:

1    UserParam up = new UserParam() { UserAdd = txtUserAdd.Text, UserName = txtUserName.Text, UserPhone = txtUserPhone.Text, UserSex = txtUserSex.Text };2    CreateUserInfoHelper creatUser = new CreateUserInfoHelper(up);3    creatUser.CreateProcess += new EventHandler<CreateUserInfoHelper.CreateArgs>(CreateProcess); //注册事件4    creatUser.Create();5    processPanel.Visibility = Visibility.Visible; 
 1    private void CreateProcess(object sender, CreateUserInfoHelper.CreateArgs args)//响应时间执行 2         { 3                 processBar.Value =http://www.mamicode.com/ args.process; 4                 processInfo.Text = String.Format("创建进度:{0}/100",args.process); 5                 if (args.isFinish) 6                 { 7                     if (args.userInfo != null) 8                     { 9                         ObservableCollection<UserParam> data = http://www.mamicode.com/(ObservableCollection)dg.DataContext;10                         data.Add(args.userInfo);11                         dg.DataContext = data;12                     }13                     processPanel.Visibility = Visibility.Hidden;14                     ClearForm();15                 }16         }

  CreateUserInfoHelper帮助类代码如下:

 1    public class CreateUserInfoHelper 2     { 3         //执行进度事件(响应注册的事件) 4         public event EventHandler<CreateArgs> CreateProcess;         5          6         //待创建信息 7         public UserParam up { get; set; }        8          9         public CreateUserInfoHelper(UserParam _up)10         {11             up = _up;12         }13 14         public void Create()15         {16             Thread t = new Thread(Start);//抛出一个行线程17             t.Start();18         }19 20         private void Start()21         {22             try23             {24                 //ToDo:编写创建用户的DataAccess代码25                 for (Int32 idx = 1; idx <= 10; idx++)26                 {27                     CreateProcess(this, new CreateArgs()28                     {29                         isFinish = ((idx == 10) ? true : false),30                         process = idx * 10,31                         userInfo =null32                     });33                     Thread.Sleep(1000);34                 }35 36                 CreateProcess(this, new CreateArgs()37                 {38                     isFinish = true,39                     process = 100,40                     userInfo =up41                 });42             }43             catch (Exception ex)44             {45                 CreateProcess(this, new CreateArgs()46                 {47                     isFinish = true,48                     process = 100,49                     userInfo = null50                 });51             }52         }53 54         /// <summary>55         /// 创建步骤反馈参数56         /// </summary>57         public class CreateArgs : EventArgs58         {59             /// <summary>60             /// 是否创建结束61             /// </summary>62             public Boolean isFinish { get; set; }63             /// <summary>64             /// 进度65             /// </summary>66             public Int32 process { get; set; }67             /// <summary>68             /// 处理后的用户信息69             /// </summary>70             public UserParam userInfo { get; set; }71         }72     }

 目的很简单:就是在创建用户信息的时候,使用另外一个线程执行创建工作,最后将结果呈现在试图列表上,而在这个创建过程中会相应的呈现进度条。

来看下效果:

技术分享

立马报错了,原因很简单,在创建对象时,该操作发生在调用CreateUserInfoHelper帮助类方法所在的线程中。

对于 UI 元素,在加载 XAML 文档时,XAML 分析器会创建对象。所有这一切都在主线程上进行。因此,所有这些 UI 元素都属于主线程,这也通常称为 UI 线程。

当先前代码中的后台线程尝试修改 UI主线程的元素 属性时,则会导致非法的跨线程访问。因此会引发异常。

 

解决办法就是去通知主线程来处理UI, 通过向主线程的Dispatcher队列注册工作项,来通知UI线程更新结果。

Dispatcher提供两个注册工作项的方法:Invoke 和 BeginInvoke。

这两个方法均调度一个委托来执行。Invoke 是同步调用,也就是说,直到 UI 线程实际执行完该委托它才返回。BeginInvoke是异步的,将立即返回。

所以我们修改上面的代码如下:

 1  private void CreateProcess(object sender, CreateUserInfoHelper.CreateArgs args) 2         { 3             this.Dispatcher.BeginInvoke((Action)delegate() 4             { 5                 processBar.Value =http://www.mamicode.com/ args.process; 6                 processInfo.Text = String.Format("创建进度:{0}/100",args.process); 7                 if (args.isFinish) 8                 { 9                     if (args.userInfo != null)10                     {11                         ObservableCollection<UserParam> data = http://www.mamicode.com/(ObservableCollection)dg.DataContext;12                         data.Add(args.userInfo);13                         dg.DataContext = data;14                     }15                     processPanel.Visibility = Visibility.Hidden;16                     ClearForm();17                 }18             });19         }

  结果如下:

技术分享

技术分享

实现异步执行的结果。

MVVM 应用程序中的调度

当从 ViewModel 执行后台操作时,情况略有不同。通常,ViewModel 不从 DispatcherObject 继承。它们是执行 INotifyPropertyChanged 接口的 Plain Old CLR Objects (POCO)。

因为 ViewModel 是一个 POCO,它不能访问 Dispatcher 属性,因此我需要通过另一种方式来访问主线程,以将操作加入队列中。这是 MVVM Light DispatcherHelper 组件的作用。

实际上,该类所做的是将主线程的调度程序保存在静态属性中,并公开一些实用工具方法,以便通过便捷且一致的方式访问。为了实现正常功能,需要在主线程上初始化该类。

最好应在应用程序生命周期的初期进行此操作,使应用程序一开始便能够访问这些功能。通常,在 MVVM Light 应用程序中,DispatcherHelper 在 App.xaml.cs 中进行初始化,App.xaml.cs 是定义应用程序启动类的文件。在 Windows Phone 中,在应用程序的主框架刚刚创建之后,在 InitializePhoneApplication 方法中调用 Dispatcher­Helper.Initialize。在 WPF 中,该类是在 App 构造函数中进行初始化的。在 Windows 8 中,在窗口激活之后便立刻在 OnLaunched 中调用 Initialize 方法。

完成了对 DispatcherHelper.Initialize 方法的调用后,DispatcherHelper 类的 UIDispatcher 属性包含对主线程的调度程序的引用。相对而言很少直接使用该属性,但如果需要可以这样做。但最好使用 CheckBeginInvokeOnUi 方法。此方法将委托视为参数。

所以将上述代码改装程:

View代码(学过Bind和Command之后应该很好理解下面这段代码,没什么特别的):

 1     <Grid> 2         <Grid.Resources> 3             <Style TargetType="{x:Type Border}" x:Key="ProcessBarBorder"> 4                 <Setter Property="BorderBrush" Value=http://www.mamicode.com/"LightGray" ></Setter> 5                 <Setter Property="BorderThickness" Value=http://www.mamicode.com/"1" ></Setter> 6                 <Setter Property="Background" Value=http://www.mamicode.com/"White" ></Setter> 7             </Style> 8         </Grid.Resources> 9 10         <!-- 延迟框 -->11         <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" >12             <Border Style="{StaticResource ProcessBarBorder}" Padding="5" Visibility="{Binding IsWaitingDisplay,Converter={StaticResource boolToVisibility}}" Panel.ZIndex="999" HorizontalAlignment="Center"  VerticalAlignment="Center" Height="50">13                 <StackPanel Orientation="Vertical" VerticalAlignment="Center" >14                     <ProgressBar Value=http://www.mamicode.com/"{Binding ProcessRange}" Maximum="100" Width="400" Height="5" ></ProgressBar>15                     <TextBlock Text="{Binding ProcessRange,StringFormat=‘执行进度:\{0\}/100‘}" Margin="0,10,0,0" ></TextBlock>16                 </StackPanel>17             </Border>18         </Grid>19 20         <StackPanel Orientation="Vertical" IsEnabled="{Binding IsEnableForm}" >21             <StackPanel>22                 <DataGrid ItemsSource="{Binding UserList}" AutoGenerateColumns="False" CanUserAddRows="False" 23                                       CanUserSortColumns="False" Margin="10" AllowDrop="True" IsReadOnly="True" >24                     <DataGrid.Columns>25                         <DataGridTextColumn Header="学生姓名" Binding="{Binding UserName}" Width="100" />26                         <DataGridTextColumn Header="学生家庭地址"  Binding="{Binding UserAdd}" Width="425" >27                             <DataGridTextColumn.ElementStyle>28                                 <Style TargetType="{x:Type TextBlock}">29                                     <Setter Property="TextWrapping" Value=http://www.mamicode.com/"Wrap"/>30                                     <Setter Property="Height" Value=http://www.mamicode.com/"auto"/>31                                 </Style>32                             </DataGridTextColumn.ElementStyle>33                         </DataGridTextColumn>34                         <DataGridTextColumn Header="电话" Binding="{Binding UserPhone}" Width="100" />35                         <DataGridTextColumn Header="性别" Binding="{Binding UserSex}" Width="100" />36                     </DataGrid.Columns>37                 </DataGrid>38             </StackPanel>39 40             <StackPanel Orientation="Horizontal"  Margin="10,10,10,10">41                 <StackPanel Orientation="Vertical" Margin="0,0,10,0" >42                     <StackPanel Orientation="Horizontal" Margin="0,0,0,5" >43                         <TextBlock Text="学生姓名" Width="80" ></TextBlock>44                         <TextBox Text="{Binding User.UserName}" Width="200" />45                     </StackPanel>46                     <StackPanel Orientation="Horizontal" Margin="0,0,0,5">47                         <TextBlock Text="学生电话" Width="80" ></TextBlock>48                         <TextBox Text="{Binding User.UserPhone}" Width="200" />49                     </StackPanel>50                     <StackPanel Orientation="Horizontal" Margin="0,0,0,5">51                         <TextBlock Text="学生家庭地址" Width="80"></TextBlock>52                         <TextBox Text="{Binding User.UserAdd}" Width="200"/>53                     </StackPanel>54                     <StackPanel Orientation="Horizontal" Margin="0,0,0,5" >55                         <TextBlock Text="学生性别" Width="80" ></TextBlock>56                         <TextBox Text="{Binding User.UserSex}" Width="200" />57                     </StackPanel>58                     <StackPanel>59                         <Button Content="提交" Width="100" Command="{Binding AddRecordCmd}" ></Button>60                     </StackPanel>61                 </StackPanel>62             </StackPanel>63 64         </StackPanel>65     </Grid>

 

ViewModel代码:

(先初始化 DispatcherHelper,再调用 CheckBeginInvokeOnUI 方法来实现对UI线程的调度)

  1  public class DispatcherHelperViewModel:ViewModelBase  2     {  3         /// <summary>  4         /// 构造行数  5         /// </summary>  6         public DispatcherHelperViewModel()  7         {  8             InitData();  9             DispatcherHelper.Initialize(); 10         } 11  12  13         #region 全局属性 14  15         private ObservableCollection<UserParam> userList; 16         /// <summary> 17         /// 数据列表 18         /// </summary> 19         public ObservableCollection<UserParam> UserList 20         { 21             get { return userList; } 22             set { userList = value; RaisePropertyChanged(() => UserList); } 23         }                24  25         private UserParam user; 26         /// <summary> 27         /// 当前用户信息 28         /// </summary> 29         public UserParam User 30         { 31             get { return user; } 32             set { user = value; RaisePropertyChanged(()=>User); } 33         } 34  35  36         private Boolean isEnableForm; 37         /// <summary> 38         /// 是否表单可用 39         /// </summary> 40         public bool IsEnableForm 41         { 42             get { return isEnableForm; } 43             set { isEnableForm = value; RaisePropertyChanged(()=>IsEnableForm); } 44         } 45          46         private Boolean isWaitingDisplay; 47         /// <summary> 48         /// 是都显示延迟旋转框 49         /// </summary> 50         public bool IsWaitingDisplay 51         { 52             get{ return isWaitingDisplay; } 53             set{ isWaitingDisplay = value; RaisePropertyChanged(()=>IsWaitingDisplay);} 54         } 55          56         private Int32 processRange; 57         /// <summary> 58         /// 进度比例 59         /// </summary> 60         public int ProcessRange 61         { 62             get { return processRange; } 63             set { processRange = value; RaisePropertyChanged(()=>ProcessRange);} 64         } 65  66         #endregion 67  68  69         #region 全局命令 70         private RelayCommand addRecordCmd; 71         /// <summary> 72         /// 添加资源 73         /// </summary> 74         public RelayCommand AddRecordCmd 75         { 76             get 77             { 78                 if (addRecordCmd == null) addRecordCmd = new RelayCommand(()=>ExcuteAddRecordCmd());                     79                 return addRecordCmd; 80             } 81             set 82             { 83                 addRecordCmd = value; 84             } 85         } 86         #endregion 87  88  89         #region 辅助方法 90         /// <summary> 91         /// 初始化数据 92         /// </summary> 93         private void InitData() 94         { 95             UserList = new ObservableCollection<UserParam>() 96             { 97                  new UserParam(){ UserName="周杰伦", UserAdd="周杰伦地址", UserPhone ="88888888", UserSex="" }, 98                  new UserParam(){ UserName="刘德华", UserAdd="刘德华地址", UserPhone ="88888888", UserSex="" }, 99                  new UserParam(){ UserName="刘若英", UserAdd="刘若英地址", UserPhone ="88888888", UserSex="" }100             };101             User = new UserParam();102             IsEnableForm = true;103             IsWaitingDisplay = false;104         }105 106         /// <summary>107         /// 执行命令108         /// </summary>109         private void ExcuteAddRecordCmd()110         {111             UserParam up = new UserParam { UserAdd = User.UserAdd, UserName = User.UserName, UserPhone = User.UserPhone, UserSex = User.UserSex };112             CreateUserInfoHelper creatUser = new CreateUserInfoHelper(up);113             creatUser.CreateProcess += new EventHandler<CreateUserInfoHelper.CreateArgs>(CreateProcess);114             creatUser.Create();115             IsEnableForm = false;116             IsWaitingDisplay = true;117         }118         119         /// <summary>120         /// 创建进度121         /// </summary>122         /// <param name="sender"></param>123         /// <param name="args"></param>124         private void CreateProcess(object sender, CreateUserInfoHelper.CreateArgs args)125         {126             DispatcherHelper.CheckBeginInvokeOnUI(() =>127             {128                 if (args.isFinish)129                 {130                     if (args.userInfo != null)131                     {132                         UserList.Add(args.userInfo);133                     }134 135                     IsEnableForm = true;136                     IsWaitingDisplay = false;137                 }138                 else139                 {140                     ProcessRange = args.process;141                 }                142             });143         }144         #endregion145 146     }

 结果如下:

技术分享

 

 示例代码下载

转载请注明出处,谢谢

利刃 MVVMLight 8:DispatchHelper在多线程和调度中的使用