利刃 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 = 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 = (ObservableCollection<UserParam>)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 try 23 { 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 =null 32 }); 33 Thread.Sleep(1000); 34 } 35 36 CreateProcess(this, new CreateArgs() 37 { 38 isFinish = true, 39 process = 100, 40 userInfo =up 41 }); 42 } 43 catch (Exception ex) 44 { 45 CreateProcess(this, new CreateArgs() 46 { 47 isFinish = true, 48 process = 100, 49 userInfo = null 50 }); 51 } 52 } 53 54 /// <summary> 55 /// 创建步骤反馈参数 56 /// </summary> 57 public class CreateArgs : EventArgs 58 { 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 = 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 = (ObservableCollection<UserParam>)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 方法中调用 DispatcherHelper.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="LightGray" ></Setter> 5 <Setter Property="BorderThickness" Value="1" ></Setter> 6 <Setter Property="Background" Value="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="{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="Wrap"/> 30 <Setter Property="Height" Value="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 else 139 { 140 ProcessRange = args.process; 141 } 142 }); 143 } 144 #endregion 145 146 }
结果如下:
转载请注明出处,谢谢