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

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

在很多情况下,我们启动新的线程主目的是执行操作(或等待某个操作的结果),而不会导致应用程序的其余部分被阻塞。密集型计算操作、高并发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. Mac下tomcat配置ssl

    最近在搞单点登录CAS,第一步就是需要给tomcat配置证书.但是,第一次配置就遇到了个问题排插了一下午.下面来存一份文档,以备以后遇到. 一.首先准备好环境 java环境:配置好环境变量,找到jdk ...

  2. HTML5 移动页面自适应手机屏幕四类方法

    1.使用meta标签:viewport H5移动端页面自适应普遍使用的方法,理论上讲使用这个标签是可以适应所有尺寸的屏幕的,但是各设备对该标签的解释方式及支持程度不同造成了不能兼容所有浏览器或系统. ...

  3. jQuery kxbdMarquee 无缝滚动

    转:http://code.ciaoca.com/jquery/kxbdmarquee/ <marquee> 曾是 IE 下独有的一个走马灯效果的标签,其他浏览器并不兼容,于是出现了使用 ...

  4. 用js控制css属性

    在用js控制css属性时,行内css属性可以任意控制,但若是在<style></style>中写的css属性,均不能用alert读取,但是赋值却有几种现象, 第一种:无法读取, ...

  5. POP3是收邮件的协议,SMTP是发邮件的协议,IMAP是一种邮箱通信协议。

    我也是第一次接触这种服务,是因为我自己在做一个小小的自动推送天气情况到自己邮箱.所以才碰到这个的/ 看一下标题,我们可以先这样理解. POP3(Post Office Protocol - Versi ...

  6. 初见 ThreadLocal 类

    这个类能够将一个对象和一个线程绑定起来. 之所以写这个类是因为 DBUtils 工具类,在 JavaEE 经典三层结构中对于事务的操作,不方便放在 DAO 层,因为具有侵入性,只适合放在 Servic ...

  7. 000 Python之禅

    The Zen of Python, by Tim Peters Beautiful is better than ugly.Explicit is better than implicit.Simp ...

  8. iOS开发之如何修改导航栏的内容

    导航栏的内容由栈顶控制器的navigationItem属性决定. UINavigationItem有以下属性影响着导航栏的内容(通常在子控制器中viewDidLoad方法中调用这些方法): 左上角的返 ...

  9. python实现mysql的读写分离及负载均衡

    Oracle数据库有其公司开发的配套rac来实现负载均衡,目前已知的最大节点数能到128个,但是其带来的维护成本无疑是很高的,并且rac的稳定性也并不是特别理想,尤其是节点很多的时候. 但是,相对my ...

  10. String 类的实现(5)String常用函数

      2 #include<iostream> 3 #include<stdio.h> 4 #include<assert.h> 5 #include <iom ...