zoukankan      html  css  js  c++  java
  • 利刃 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 方法中调用 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="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     }

     结果如下:

     示例代码下载

    转载请注明出处,谢谢

  • 相关阅读:
    多测师讲解html _伪类选择器17_高级讲师肖sir
    多测师讲解html _后代选择器16_高级讲师肖sir
    多测师讲解html _组合选择器_高级讲师肖sir
    多测师讲解html _标签选择器14_高级讲师肖sir
    前端 HTML form表单标签 input标签 type属性 重置按钮 reset
    前端 HTML form表单标签 textarea标签 多行文本
    前端 HTML form表单标签 input标签 type属性 file 上传文件
    前端 HTML form表单标签 input标签 type属性 radio 单选框
    前端 HTML form表单标签 input标签 type属性 checkbox 多选框
    前端 HTML form表单目录
  • 原文地址:https://www.cnblogs.com/wzh2010/p/6633026.html
Copyright © 2011-2022 走看看