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

结果如下:

示例代码下载

转载请注明出处,谢谢

时间: 2024-10-22 09:02:10

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

利刃 MVVMLight

已经很久没有写系列文章了,上一次是2012年写的HTLM5系列,想想我们应该是较早一批使用HTML5做项目的人. 相比我当时动不动100+的粉丝增长和两天3000+的阅读量,MVVM Light只能算小众技术了,也是因为她小众,才更具意义,不希望有人跟我一样网上找案例找半天. 写的初衷也是希望同项目组的同事能更好的融入和接收已有项目.其中两篇<DispatchHelper在多线程和调度中的使用>和<Messenger>是在上林院长课的时候抱着笔记本写的,现在想想挺后悔的, 再回头上

利刃 MVVMLight 2:Model、View、ViewModel结构以及全局视图模型注入器的说明

上一篇我们已经介绍了如何使用NuGet把MVVMLight应用到我们的WPF项目中.这篇我们来了解下一个基本的MVVMLight框架所必须的结构和运行模式. MVVMLight安装之后,我们可以看到简易的框架布局,如上篇,生成了一个ViewModel文件夹,ViewModel层的内容都放在这边,除了Main对象的ViewModel之外,还包含一个ViewModelLocator文件, 用来注入当前的ViewModel全局实例. 一.先来说说分层结构: 如图: 1.View负责前端展示,与View

多线程(三) java中线程的简单使用

============================================= 原文链接:多线程(三) java中线程的简单使用 转载请注明出处! ============================================= java中,启动线程通常是通过Thread或其子类通过调用start()方法启动. 常见使用线程有两种:实现Runnable接口和继承Thread.而继承Thread亦或使用TimerTask其底层依旧是实现了Runnabel接口.考虑到java的

转载~kxcfzyk:Linux C语言多线程库Pthread中条件变量的的正确用法逐步详解

Linux C语言多线程库Pthread中条件变量的的正确用法逐步详解 多线程c语言linuxsemaphore条件变量 (本文的读者定位是了解Pthread常用多线程API和Pthread互斥锁,但是对条件变量完全不知道或者不完全了解的人群.如果您对这些都没什么概念,可能需要先了解一些基础知识) 关于条件变量典型的实际应用,可以参考非常精简的Linux线程池实现(一)——使用互斥锁和条件变量,但如果对条件变量不熟悉最好先看完本文. Pthread库的条件变量机制的主要API有三个: int p

HttpContext在多线程异步调用中的使用方案

1.在线程调用中,有时候会碰到操作文件之类的功能.对于开发人员来说,他们并不知道网站会被部署在服务器的那个角落里面,因此根本无法确定真实的物理路径(当然可以使用配置文件来配置物理路径),他们唯一知道的就是文件在项目中的相对路径,为了定位文件路径,一般都会调用HttpContext.Current.Request.MapPath或者HttpContext.Current.Server.MapPath,但是在多线程调用中,HttpContext肯定为null,这时候还调用MapPath结果就是报错.

利刃 MVVMLight 3:双向数据绑定

上篇我们已经了解了MVVM的框架结构和运行原理.这里我们来看一下伟大的双向数据绑定. 说到双向绑定,大家比较熟悉的应该就是AngularJS了,几乎所有的AngularJS 系列教程的开篇几章都要涉及到,真的是很好用. 表达的效果很简单:就是在界面的操作对数据模型的修改能实时反映到数据:而数据的变更能实时展现到界面.即视图数据模型(ViewModel)和视图(View)之间的双向绑定和触发. 我们来操作一个试试看: 第一步:先写一个Model,里面包含我们需要的数据信息,代码如下: 1 ///

利刃 MVVMLight 1:MVVMLight介绍以及在项目中的使用

一.MVVM 和 MVVMLight介绍 MVVM是Model-View-ViewModel的简写.类似于目前比较流行的MVC.MVP设计模式,主要目的是为了分离视图(View)和模型(Model)的耦合. 它是一种极度优秀的设计模式,但并非框架级别的东西,由MVP(Model-View-Presenter)模式与WPF结合的应用方式时发展演变过来的一种新型架构. 立足于原有MVP框架并且把WPF的新特性糅合进去,以应对PC端开发日益复杂的需求变化. 结构如图所示: 相对于之前把逻辑结构写在Co

利刃 MVVMLight 5:绑定在表单验证上的应用

表单验证是MVVM体系中的重要一块.而绑定除了推动 Model-View-ViewModel (MVVM) 模式松散耦合 逻辑.数据 和 UI定义 的关系之外,还为业务数据验证方案提供强大而灵活的支持. WPF 中的数据绑定机制包括多个选项,可用于在创建可编辑视图时校验输入数据的有效性. 常见的表单验证机制有如下几种: 验证类型 说明 Exception 验证 通过在某个 Binding 对象上设置 ValidatesOnExceptions 属性,如果源对象属性设置已修改的值的过程中引发异常,

利刃 MVVMLight 7:命令深入

上面一篇我们大致了解了命令的基本使用方法和基础原理,但是实际在运用命令的时候会复杂的多,并且会遇到各种各样的情况. 一.命令带参数的情况: 如果视图控件所绑定的命令想要传输参数,需要配置 CommandParameter 属性 ,用来传输参数出去. 而继承制Icommand接口的 RelayCommand又支持泛型的能力,这样就可以接受来自客户端请求的参数. public RelayCommand(Action<T> execute);构造函数传入的是委托类型的参数,Execute 和 Can