• 利刃 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     }

     结果如下:

     示例代码下载

    转载请注明出处,谢谢

  • 相关阅读:
    PHP之PHPExcel X
    Docker之基础(一) X
    Django Admin之常用功能汇总 X
    pycharm中配置启动Django项目 X
    银联支付接入新一代 X
    pycharm集成Jupyter Notebook X
    Django之model外键 X
    Django之添加prometheus监控 X
    Yii2 中配置方法汇总 X
    python常用模块汇总 X
  • 原文地址:https://www.cnblogs.com/wzh2010/p/6633026.html
Copyright © 2020-2023  润新知