利刃 MVVMLight 8:DispatchHelper在多线程和调度中的使用
在应用程序中,线程可以被看做是应用程序的一个较小的执行单位。每个应用程序都至少拥有一个线程,我们称为主线程,这是在启动时调用应用程序的主方法时由操作系统分配启动的线程。
当调用和操作主线程的时候,该操作将动作添加到一个队列中。每个操作均按照将它们添加到队列中的顺序连续执行,但是可以通过为这些动作指定优先级来影响执行顺序,而负责管理此队列的对象称之为线程调度程序。
在很多情况下,我们启动新的线程主目的是执行操作(或等待某个操作的结果),而不会导致应用程序的其余部分被阻塞。密集型计算操作、高并发I/O操作等都是这种情况,所以现在的复杂应用程序日益多线程化了。
当我们启动一个应用程序并创建对象时,就会调用构造函数方法所在的线程,对于 UI 元素,在加载 XAML 文档时,XAML 分析器会创建基于这些UI元素的对象。所以所有的对象(包括UI元素)的创建都归属于当前的主线程,当然也只有主线程可以访问他们。
但在实际情况中,有很多情况是要假手其他线程来处理的。
比如在一个长交互中,我们可能需要而外的线程来处理复杂的执行过程,以免造成线程阻塞,给用户界面卡死的错觉。

比如下面这个例子,我们使用委托的方式模拟用户执行数据创建的操作:
调用CreateUserInfoHelper帮助类 和 执行 CreateProcess方法 的代码如下:
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); //注册事件
creatUser.Create();
processPanel.Visibility = Visibility.Visible;
private void CreateProcess(object sender, CreateUserInfoHelper.CreateArgs args)//响应时间执行
{
processBar.Value = args.process;
processInfo.Text = String.Format("创建进度:{0}/100",args.process);
if (args.isFinish)
{
if (args.userInfo != null)
{
ObservableCollection<UserParam> data = (ObservableCollection<UserParam>)dg.DataContext;
data.Add(args.userInfo);
dg.DataContext = data;
}
processPanel.Visibility = Visibility.Hidden;
ClearForm();
}
}
CreateUserInfoHelper帮助类代码如下:
public class CreateUserInfoHelper
{
//执行进度事件(响应注册的事件)
public event EventHandler<CreateArgs> CreateProcess; //待创建信息
public UserParam up { get; set; } public CreateUserInfoHelper(UserParam _up)
{
up = _up;
} public void Create()
{
Thread t = new Thread(Start);//抛出一个行线程
t.Start();
} private void Start()
{
try
{
//ToDo:编写创建用户的DataAccess代码
for (Int32 idx = ; idx <= ; idx++)
{
CreateProcess(this, new CreateArgs()
{
isFinish = ((idx == ) ? true : false),
process = idx * ,
userInfo =null
});
Thread.Sleep();
} CreateProcess(this, new CreateArgs()
{
isFinish = true,
process = ,
userInfo =up
});
}
catch (Exception ex)
{
CreateProcess(this, new CreateArgs()
{
isFinish = true,
process = ,
userInfo = null
});
}
} /// <summary>
/// 创建步骤反馈参数
/// </summary>
public class CreateArgs : EventArgs
{
/// <summary>
/// 是否创建结束
/// </summary>
public Boolean isFinish { get; set; }
/// <summary>
/// 进度
/// </summary>
public Int32 process { get; set; }
/// <summary>
/// 处理后的用户信息
/// </summary>
public UserParam userInfo { get; set; }
}
}
目的很简单:就是在创建用户信息的时候,使用另外一个线程执行创建工作,最后将结果呈现在试图列表上,而在这个创建过程中会相应的呈现进度条。
来看下效果:

立马报错了,原因很简单,在创建对象时,该操作发生在调用CreateUserInfoHelper帮助类方法所在的线程中。
对于 UI 元素,在加载 XAML 文档时,XAML 分析器会创建对象。所有这一切都在主线程上进行。因此,所有这些 UI 元素都属于主线程,这也通常称为 UI 线程。
当先前代码中的后台线程尝试修改 UI主线程的元素 属性时,则会导致非法的跨线程访问。因此会引发异常。
解决办法就是去通知主线程来处理UI, 通过向主线程的Dispatcher队列注册工作项,来通知UI线程更新结果。
Dispatcher提供两个注册工作项的方法:Invoke 和 BeginInvoke。
这两个方法均调度一个委托来执行。Invoke 是同步调用,也就是说,直到 UI 线程实际执行完该委托它才返回。BeginInvoke是异步的,将立即返回。
所以我们修改上面的代码如下:
private void CreateProcess(object sender, CreateUserInfoHelper.CreateArgs args)
{
this.Dispatcher.BeginInvoke((Action)delegate()
{
processBar.Value = args.process;
processInfo.Text = String.Format("创建进度:{0}/100",args.process);
if (args.isFinish)
{
if (args.userInfo != null)
{
ObservableCollection<UserParam> data = (ObservableCollection<UserParam>)dg.DataContext;
data.Add(args.userInfo);
dg.DataContext = data;
}
processPanel.Visibility = Visibility.Hidden;
ClearForm();
}
});
}
结果如下:


实现异步执行的结果。
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之后应该很好理解下面这段代码,没什么特别的):
<Grid>
<Grid.Resources>
<Style TargetType="{x:Type Border}" x:Key="ProcessBarBorder">
<Setter Property="BorderBrush" Value="LightGray" ></Setter>
<Setter Property="BorderThickness" Value="" ></Setter>
<Setter Property="Background" Value="White" ></Setter>
</Style>
</Grid.Resources> <!-- 延迟框 -->
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" >
<Border Style="{StaticResource ProcessBarBorder}" Padding="" Visibility="{Binding IsWaitingDisplay,Converter={StaticResource boolToVisibility}}" Panel.ZIndex="" HorizontalAlignment="Center" VerticalAlignment="Center" Height="">
<StackPanel Orientation="Vertical" VerticalAlignment="Center" >
<ProgressBar Value="{Binding ProcessRange}" Maximum="" Width="" Height="" ></ProgressBar>
<TextBlock Text="{Binding ProcessRange,StringFormat='执行进度:\{0\}/100'}" Margin="0,10,0,0" ></TextBlock>
</StackPanel>
</Border>
</Grid> <StackPanel Orientation="Vertical" IsEnabled="{Binding IsEnableForm}" >
<StackPanel>
<DataGrid ItemsSource="{Binding UserList}" AutoGenerateColumns="False" CanUserAddRows="False"
CanUserSortColumns="False" Margin="" AllowDrop="True" IsReadOnly="True" >
<DataGrid.Columns>
<DataGridTextColumn Header="学生姓名" Binding="{Binding UserName}" Width="" />
<DataGridTextColumn Header="学生家庭地址" Binding="{Binding UserAdd}" Width="" >
<DataGridTextColumn.ElementStyle>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="TextWrapping" Value="Wrap"/>
<Setter Property="Height" Value="auto"/>
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="电话" Binding="{Binding UserPhone}" Width="" />
<DataGridTextColumn Header="性别" Binding="{Binding UserSex}" Width="" />
</DataGrid.Columns>
</DataGrid>
</StackPanel> <StackPanel Orientation="Horizontal" Margin="10,10,10,10">
<StackPanel Orientation="Vertical" Margin="0,0,10,0" >
<StackPanel Orientation="Horizontal" Margin="0,0,0,5" >
<TextBlock Text="学生姓名" Width="" ></TextBlock>
<TextBox Text="{Binding User.UserName}" Width="" />
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,0,0,5">
<TextBlock Text="学生电话" Width="" ></TextBlock>
<TextBox Text="{Binding User.UserPhone}" Width="" />
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,0,0,5">
<TextBlock Text="学生家庭地址" Width=""></TextBlock>
<TextBox Text="{Binding User.UserAdd}" Width=""/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,0,0,5" >
<TextBlock Text="学生性别" Width="" ></TextBlock>
<TextBox Text="{Binding User.UserSex}" Width="" />
</StackPanel>
<StackPanel>
<Button Content="提交" Width="" Command="{Binding AddRecordCmd}" ></Button>
</StackPanel>
</StackPanel>
</StackPanel> </StackPanel>
</Grid>
ViewModel代码:
(先初始化 DispatcherHelper,再调用 CheckBeginInvokeOnUI 方法来实现对UI线程的调度)
public class DispatcherHelperViewModel:ViewModelBase
{
/// <summary>
/// 构造行数
/// </summary>
public DispatcherHelperViewModel()
{
InitData();
DispatcherHelper.Initialize();
} #region 全局属性 private ObservableCollection<UserParam> userList;
/// <summary>
/// 数据列表
/// </summary>
public ObservableCollection<UserParam> UserList
{
get { return userList; }
set { userList = value; RaisePropertyChanged(() => UserList); }
} private UserParam user;
/// <summary>
/// 当前用户信息
/// </summary>
public UserParam User
{
get { return user; }
set { user = value; RaisePropertyChanged(()=>User); }
} private Boolean isEnableForm;
/// <summary>
/// 是否表单可用
/// </summary>
public bool IsEnableForm
{
get { return isEnableForm; }
set { isEnableForm = value; RaisePropertyChanged(()=>IsEnableForm); }
} private Boolean isWaitingDisplay;
/// <summary>
/// 是都显示延迟旋转框
/// </summary>
public bool IsWaitingDisplay
{
get{ return isWaitingDisplay; }
set{ isWaitingDisplay = value; RaisePropertyChanged(()=>IsWaitingDisplay);}
} private Int32 processRange;
/// <summary>
/// 进度比例
/// </summary>
public int ProcessRange
{
get { return processRange; }
set { processRange = value; RaisePropertyChanged(()=>ProcessRange);}
} #endregion #region 全局命令
private RelayCommand addRecordCmd;
/// <summary>
/// 添加资源
/// </summary>
public RelayCommand AddRecordCmd
{
get
{
if (addRecordCmd == null) addRecordCmd = new RelayCommand(()=>ExcuteAddRecordCmd());
return addRecordCmd;
}
set
{
addRecordCmd = value;
}
}
#endregion #region 辅助方法
/// <summary>
/// 初始化数据
/// </summary>
private void InitData()
{
UserList = new ObservableCollection<UserParam>()
{
new UserParam(){ UserName="周杰伦", UserAdd="周杰伦地址", UserPhone ="", UserSex="男" },
new UserParam(){ UserName="刘德华", UserAdd="刘德华地址", UserPhone ="", UserSex="男" },
new UserParam(){ UserName="刘若英", UserAdd="刘若英地址", UserPhone ="", UserSex="女" }
};
User = new UserParam();
IsEnableForm = true;
IsWaitingDisplay = false;
} /// <summary>
/// 执行命令
/// </summary>
private void ExcuteAddRecordCmd()
{
UserParam up = new UserParam { UserAdd = User.UserAdd, UserName = User.UserName, UserPhone = User.UserPhone, UserSex = User.UserSex };
CreateUserInfoHelper creatUser = new CreateUserInfoHelper(up);
creatUser.CreateProcess += new EventHandler<CreateUserInfoHelper.CreateArgs>(CreateProcess);
creatUser.Create();
IsEnableForm = false;
IsWaitingDisplay = true;
} /// <summary>
/// 创建进度
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
private void CreateProcess(object sender, CreateUserInfoHelper.CreateArgs args)
{
DispatcherHelper.CheckBeginInvokeOnUI(() =>
{
if (args.isFinish)
{
if (args.userInfo != null)
{
UserList.Add(args.userInfo);
} IsEnableForm = true;
IsWaitingDisplay = false;
}
else
{
ProcessRange = args.process;
}
});
}
#endregion }
结果如下:

转载请注明出处,谢谢
利刃 MVVMLight 8:DispatchHelper在多线程和调度中的使用的更多相关文章
- 利刃 MVVMLight 10:Messenger 深入
1.Messager交互结构和消息类型 衔接上篇,Messeger是信使的意思,顾名思义,他的目是用于View和ViewModel 以及 ViewModel和ViewModel 之间的消息通知和接收. ...
- 利刃 MVVMLight
已经很久没有写系列文章了,上一次是2012年写的HTLM5系列,想想我们应该是较早一批使用HTML5做项目的人. 相比我当时动不动100+的粉丝增长和两天3000+的阅读量,MVVM Light只能算 ...
- 利刃 MVVMLight 2:Model、View、ViewModel结构以及全局视图模型注入器的说明
上一篇我们已经介绍了如何使用NuGet把MVVMLight应用到我们的WPF项目中.这篇我们来了解下一个基本的MVVMLight框架所必须的结构和运行模式. MVVMLight安装之后,我们 ...
- 转载~kxcfzyk:Linux C语言多线程库Pthread中条件变量的的正确用法逐步详解
Linux C语言多线程库Pthread中条件变量的的正确用法逐步详解 多线程c语言linuxsemaphore条件变量 (本文的读者定位是了解Pthread常用多线程API和Pthread互斥锁 ...
- 为什么多线程、junit 中无法使用spring 依赖注入?
为什么多线程.junit 中无法使用spring 依赖注入? 这个问题,其实体现了,我们对spring已依赖太深,以至于不想自己写实例了. 那么到底是为什么在多线程和junit单元测试中不能使用依赖注 ...
- 多线程(三) java中线程的简单使用
java中,启动线程通常是通过Thread或其子类通过调用start()方法启动. 常见使用线程有两种:实现Runnable接口和继承Thread.而继承Thread亦或使用TimerTask其底层依 ...
- 刀哥多线程之调度组gcd-12-group
调度组 常规用法 - (void)group1 { // 1. 调度组 dispatch_group_t group = dispatch_group_create(); // 2. 队列 dispa ...
- 利刃 MVVMLight 1:MVVMLight介绍以及在项目中的使用
一.MVVM 和 MVVMLight介绍 MVVM是Model-View-ViewModel的简写.类似于目前比较流行的MVC.MVP设计模式,主要目的是为了分离视图(View)和模型(Model)的 ...
- 利刃 MVVMLight 3:双向数据绑定
上篇我们已经了解了MVVM的框架结构和运行原理.这里我们来看一下伟大的双向数据绑定. 说到双向绑定,大家比较熟悉的应该就是AngularJS了,几乎所有的AngularJS 系列教程的开篇 ...
随机推荐
- ZJOI2017 Day1
私のZJOI Day1 2017-3-21 07:52:53 有人在暴力膜 苟-- 富贵 无相忘 ZJOI2017交流群 133135071 如果你足够厉害 如果你足够厉害 如果你足够厉害 其实完全可 ...
- ATM取款~~
package com.jredu.ch03; import java.util.Scanner; public class Atmmmmmmmmmm { static int totalMoney= ...
- Android中查看布局文件中的控件(view,id)在哪里被调用(使用)
在阅读别人的代码时通常是很痛苦的,有时很想要看一看布局中的控件在哪里被调用了,为之很苦恼 在这里提供一种方法. 复制要查看的控件ID,到R文件中搜索到该ID, 接下来就好办的了,选中ID按下C ...
- CSS.05 -- 规避脱标 定位的盒子居中、CSS标签规范、溢出隐藏、内容移除(网页优化)、CSS精灵图
规避脱标 定位的盒子居中显示 Margin:0 auto : 只能让标准流的盒子居中对齐 当A是B的父系,B可以使用 margin-left:auto: 来获得相当于定位right:0:的效果 M ...
- [Openfire]使用WebSocket建立Openfire的客户端
近日工作闲暇之余,对IM系统产生了兴趣,转而研究了IM的内容.找了半天,知道比较流行的是Openfire的系统,Openfire有许多平台实现,由于我是做Web的,所以当然是希望寻找Web的实现.Op ...
- Java Swing客户端小项目
记录一下两个用java swing写的客户端. 项目1: 关键词:swing jtable 代码如下: 1.主类: package com.my.agent.client; import java. ...
- Unity无缝循环世界实现
一年前曾经碰到过已无限世界为题材的游戏开发比赛,虽然对比赛没有兴趣,但是对这个题材倒是有点想法.如何通过unity3d实现无缝的循环世界呢. 有一种想法是动态生成,一块场景一块场景进行动态加载.(做过 ...
- JavaScript写一个表格排序类
依稀记得那是上个星期六的下午,我参加了网易暑期实习生招聘笔试.考得相当糟糕,编程题3个题通过了2个,简答题没做对,选择题貌似是20个题猜了6-7个,99%是挂了,唉唉唉!生活不只眼前的苟且,学习的脚步 ...
- mysql中 decimal、numeric数据类型
例 如:salary DECIMAL(5,2) 在这个例子中,5 (精度(precision)) 代表重要的十进制数字的数目,2 (数据范围(scale)) 代表在小数点后的数字位数.在这种情况下,因 ...
- JavaWeb总结(八)—EL表达式
一.EL表达式简介 EL全名Expression Language.主要有以下作用. 1.获取数据 EL表达式主要用于替换JSP页面的脚本表达式,以及各种类型的Web域中检索Java对象.获取数据.( ...