在应用程序中,线程可以被看做是应用程序的一个较小的执行单位。每个应用程序都至少拥有一个线程,我们称为主线程,这是在启动时调用应用程序的主方法时由操作系统分配启动的线程。

当调用和操作主线程的时候,该操作将动作添加到一个队列中。每个操作均按照将它们添加到队列中的顺序连续执行,但是可以通过为这些动作指定优先级来影响执行顺序,而负责管理此队列的对象称之为线程调度程序。

在很多情况下,我们启动新的线程主目的是执行操作(或等待某个操作的结果),而不会导致应用程序的其余部分被阻塞。密集型计算操作、高并发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 方法中调用 Dispatcher­Helper.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在多线程和调度中的使用的更多相关文章

  1. 利刃 MVVMLight 10:Messenger 深入

    1.Messager交互结构和消息类型 衔接上篇,Messeger是信使的意思,顾名思义,他的目是用于View和ViewModel 以及 ViewModel和ViewModel 之间的消息通知和接收. ...

  2. 利刃 MVVMLight

    已经很久没有写系列文章了,上一次是2012年写的HTLM5系列,想想我们应该是较早一批使用HTML5做项目的人. 相比我当时动不动100+的粉丝增长和两天3000+的阅读量,MVVM Light只能算 ...

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

         上一篇我们已经介绍了如何使用NuGet把MVVMLight应用到我们的WPF项目中.这篇我们来了解下一个基本的MVVMLight框架所必须的结构和运行模式. MVVMLight安装之后,我们 ...

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

    Linux C语言多线程库Pthread中条件变量的的正确用法逐步详解   多线程c语言linuxsemaphore条件变量 (本文的读者定位是了解Pthread常用多线程API和Pthread互斥锁 ...

  5. 为什么多线程、junit 中无法使用spring 依赖注入?

    为什么多线程.junit 中无法使用spring 依赖注入? 这个问题,其实体现了,我们对spring已依赖太深,以至于不想自己写实例了. 那么到底是为什么在多线程和junit单元测试中不能使用依赖注 ...

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

    java中,启动线程通常是通过Thread或其子类通过调用start()方法启动. 常见使用线程有两种:实现Runnable接口和继承Thread.而继承Thread亦或使用TimerTask其底层依 ...

  7. 刀哥多线程之调度组gcd-12-group

    调度组 常规用法 - (void)group1 { // 1. 调度组 dispatch_group_t group = dispatch_group_create(); // 2. 队列 dispa ...

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

    一.MVVM 和 MVVMLight介绍 MVVM是Model-View-ViewModel的简写.类似于目前比较流行的MVC.MVP设计模式,主要目的是为了分离视图(View)和模型(Model)的 ...

  9. 利刃 MVVMLight 3:双向数据绑定

          上篇我们已经了解了MVVM的框架结构和运行原理.这里我们来看一下伟大的双向数据绑定. 说到双向绑定,大家比较熟悉的应该就是AngularJS了,几乎所有的AngularJS 系列教程的开篇 ...

随机推荐

  1. 【iOS】7.4 定位服务->2.1.3.2 定位 - 官方框架CoreLocation 功能2:地理编码和反地理编码

    本文并非最终版本,如果想要关注更新或更正的内容请关注文集,联系方式详见文末,如有疏忽和遗漏,欢迎指正. 本文相关目录: ================== 所属文集:[iOS]07 设备工具 === ...

  2. cuda编程学习2——add

    cudaMalloc()分配的指针有使用限制,设备指针的使用限制总结如下: 1.可以将其传递给在设备上执行的函数 2.可以在设备代码中使用其进行内存的读写操作 3.可以将其传递给在主机上执行的函数 4 ...

  3. 关于binary log那些事——认真码了好长一篇

    本文介绍binlog的作用以及几个重要参数的使用方法,同时通过实验来描述binlog内部记录内容:row .statement跟mixed的设置下,记录了哪些东西,最后会简单介绍下binlog ser ...

  4. 让我的分页类获取sessionFactory

    我们知道在Hibernate里比较重要的sessionFactory,经过Spring的管理可以很好地为Spring里注入使用的bean服务(提供数据源的使用),但是,当我们所要使用的类不是像我们尝试 ...

  5. iOS开发之Run Loop

    1.概述 (1) Run Loop提供了一种异步执行代码的机制,不能并行执行任务. (2) 在主队列中,Main Run Loop直接配合任务的执行,负责处理UI事件.计时器,以及其它内核相关事件. ...

  6. Java之路——环境配置与编译运行

    本文大纲 一.开篇 二.JDK下载 三.JDK安装 四.环境配置 五.初识Java编译 六.Java与Javac 七.第一个Java程序 八.总结 九.参考资料 一.开篇 通过对之前Java之路的了解 ...

  7. httpclient源码分析之 PoolingHttpClientConnectionManager 获取连接

    PoolingHttpClientConnectionManager是一个HttpClientConnection的连接池,可以为多线程提供并发请求服务.主要作用就是分配连接,回收连接等.同一个rou ...

  8. dotweb——go语言的一个微型web框架(三)路由注册

    上一章我们讲了如何启动一个dotweb程序,本篇文章将介绍如何注册路由. router是dotweb用来管理路由的结构体,它提供了一些关于路由操作函数. app := dotweb.New() rou ...

  9. Weblogic+apache多虚拟主机

    p.MsoNormal,li.MsoNormal,div.MsoNormal { margin: 0cm; margin-bottom: .0001pt; text-align: justify; f ...

  10. 【转】air调用windows自带的虚拟键盘

    原文:http://bbs.9ria.com/blog-73243-19560.html 最近在做一个东西,需要用到虚拟键盘.刚开始准备用as3开发一套,结果突然想起来windows有个自带的虚拟键盘 ...