.NET设计篇08-线程统一取消模型和跨线程访问UI
知识需要不断积累、总结和沉淀,思考和写作是成长的催化剂,输出倒逼输入
内容目录
一、线程统一取消模型1、取消令牌2、可以中断的线程1、设计一个中断函数2、创建CancellationTokenSource对象3、启动线程4、取消线程执行二、跨线程访问UI基本方法1、Control.Invoke和BeginInvoke2、桌面退出3、编写线程安全的控件三、BackgroundWorker组件1、干活的代码2、启动任务3、结果取回4、取消任务5、进度报告四、等等
一、线程统一取消模型
线程取消在多线程开发中非常普遍,鉴于此微软在.NET4.0基础类库中引入线程统一取消模型对线程取消功能的支持。
1、取消令牌
线程统一取消模型中两个重要的类型就是CancellationToken和CancellationTokenSource
每个CancellationTokenSource对象都包容着一个“取消令牌(CancellationToken)”,并通过它的Token属性向外界展露,用法也很简单通过调用CancellationTokenSource的Cancel()方法通知取消令牌。下面有个小栗子
2、可以中断的线程
下面介绍一个可以中断的线程模型流程
1、设计一个中断函数
一个可以中断的线程函数模板看起来像下面这样
public class ThreadFuncObject
{
//通过构造函数从外界传入取消令牌
private CancellationToken _token;
public ThreadFuncObject(CancellationToken token)
{
_token = token;
}
public void DoWork()
{
//各种功能代码...
if (_token.IsCancellationRequested)
{
//完成清理工作...
//简单的处理直接return即可
//return;
//建议抛出个取消异常
throw new OperationCanceledException(_token);
}
//各种功能代码...
}
}
2、创建CancellationTokenSource对象
CancellationTokenSource tokenSource = new CancellationTokenSource();
3、启动线程
ThreadFuncObject obj = new ThreadFuncObject(tokenSource.Token);
Thread th = new Thread(obj.DoWork);
th.Start();
4、取消线程执行
tokenSource.Cancel();
二、跨线程访问UI基本方法
刚开始桌面程序开发时,在做进度条等通知UI更新的功能时,经常会遇到跨线程访问UI的问题,抛的错就是控件不能从不是创建它的线程去更改。
在.NET Framework中,所有的可视化控件都从System.Windows.Forms.Control类派生而来,考虑到跨线程访问控件的需要,Control类提供了相应的方法完成跨线程更新界面。
1、Control.Invoke和BeginInvoke
//
// 摘要:
// 在拥有此控件的基础窗口句柄的线程上执行指定的委托。
//
// 参数:
// method:
// 包含要在控件的线程上下文中调用的方法的委托。
//
// 返回结果:
// 正在被调用的委托的返回值,或者如果委托没有返回值,则为 null。
public object Invoke(Delegate method);
Invoke方法的参数是一个委托,代表在创建控件的线程中要执行的方法。实际场景中是要向UI传值的,可以使用下面的重载
//
// 摘要:
// 在拥有控件的基础窗口句柄的线程上,用指定的自变量列表执行指定委托。
//
// 参数:
// method:
// 一个方法委托,它采用的参数的数量和类型与 args 参数中所包含的相同。
//
// args:
// 作为指定方法的参数传递的对象数组。 如果此方法没有参数,该参数可以是 null。
//
// 返回结果:
// System.Object,它包含正被调用的委托返回值;如果该委托没有返回值,则为 null。
public object Invoke(Delegate method, params object[] args);
使用像下面这样
private void ThreadMethod(Object info)
{
Action<string> del = (msg) => lblInfo.Text = msg;
lblInfo.Invoke(del,info);
}
Control.Invoke是同步方法,就是当工作线程调用此方法将一个方法委托给UI线程执行以后,它必须等待UI线程执行完此方法后才能继续执行,如果UI线程很忙,工作线程可能要等待较长的时间不能工作。这可能不太合理
Control.BeginInvoke是异步方法,就是工作线程可以将一个方法传送给UI线程执行之后,继续执行下一步的任务而无需等待。
UI线程是单一的,就是来更新用户界面和接受用户响应的。它从消息队列中提取消息处理,如果某个消息执行时间较长,将会导致界面失去响应,感觉死机了。所以长时间任务要交给独立的工作线程去执行,UI线程只管向用户展示执行的结果。UI线程不应兼职过多
2、桌面退出
桌面(Windows窗体)程序是事件驱动的,应该确保用户不能频繁的点击启动访问控件的线程。因为多线程同时访问同一个控件,可能会造成程序不稳定,出现意想不到的的结果。可以通过控制按钮状态或线程状态,控制线程同步。
如果关闭主窗体导致主线程退出,工作线程还没结束。因为主窗体销毁了,其上的控件都被销毁,而工作线程还包含着访问控件的代码,所以会抛出ObjectDisposedException异常。
前几篇线程中讲到,最简单的方法就是将工作线程设置为后台线程,帮随着主线程的退出而退出,另一个方法就是在窗体FormClosing关闭事件中,判断工作线程的状态,手动Abort终止它。
3、编写线程安全的控件
我们可以将多线程访问功能封装进控件里,从而简化编写跨线程访问时的代码。比如Lable标签控件的text属性不能跨线程直接访问,可以派生出一个新的ThreadSafeLable类,向下面这样,这样不管跨不跨线程,使用相同的代码访问线程安全的控件。
public class ThreadSafeLable : Label
{
//覆盖基类的Text属性
public override string Text
{
get
{
return base.Text;
}
set
{
if (InvokeRequired)//跨线程调用
{
Action<string> del = (msg) => base.Text = msg;
Invoke(del, value);
}
else//普通调用
{
base.Text = value;
}
}
}
}
项目开发中注意封装一些这样的控件,可以简化多线程调用代码,提高项目的开发效率
三、BackgroundWorker组件
其实微软提供了一个现成的组件用于跨线程访问UI的,那就是BackgroundWorker组件,大大简化了此类程序的开发。基于事件的异步调用模式,就是通过事件告知什么时候干什么事。支持报告进度,支持取消。


1、干活的代码
在DoWork事件中,真正干活的代码放在这里
2、启动任务
调用BackgroundWorker组件的RunWorkerAsync方法,此方法会激发DoWork事件。此方法有个重载可以传Object对象,在DoWork中通过DoWorkEventArgs.Argument来接收
3、结果取回
BackgroundWorker组件有一个RunWorkerCompleted事件
其中的RunWorkerCompletedEventArgs参数包含以下重要信息:
| 参数属性 | 描述 |
|---|---|
| e.Result | 工作任务执行的结果 |
| e.Error | 这是一个Exception对象,如果工作任务执行过程中没有发生异常,则此属性为null,如果发生了异常,此属性引用被抛出的异常对象 |
| e.Cancelled | 如果在工作任务完成前用户取消了操作,则此属性为True,否则此属性为False |
4、取消任务
BackgroundWorker组件有一个CancelAsync方法,调用此方法将会导致BackgroundWorker组件的只读属性CancellationPending为True,然后在DoWork中判断即可。
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker bw = sender as BackgroundWorker;
if (bw.CancellationPending)//如果用户取消了操作
{
e.Cancel = true;//此结果将会传到RunWorkerCompleted事件中
return;//仍需要手动提交结束任务
}
//...
}
5、进度报告
BackgroundWorker组件有一个ProgressChanged事件。在DoWork事件处理代码中合适的地方调用BackgroundWorker组件的ReportProgress方法,就会激发ProgressChanged事件。ReportProgress除了可以报告进度,也可以通过其重载报告一个object对象,往往是描述信息。
在ProgressChanged事件中使用线程同步上下文做了特殊处理,可以直接访问窗体上控件,无需考虑跨线程问题。
四、等等
好多东西以前都认真看过,没记性就忘了。马上要搬家了,装不进脑子里就带不走。每次搬家还要为几本书多付一些搬家费,惆怅。(没有大家电,书本就是最重的东西了)

给我一杯酒再给我一支烟,说走就走有缘就再见
.NET设计篇08-线程统一取消模型和跨线程访问UI的更多相关文章
- .NET设计篇08-线程取消模型和跨线程访问UI
知识需要不断积累.总结和沉淀,思考和写作是成长的催化剂,输出倒逼输入 内容目录 一.线程统一取消模型1.取消令牌2.可以中断的线程1.设计一个中断函数2.创建CancellationTokenSour ...
- WPF / Win Form:多线程去修改或访问UI线程数据的方法( winform 跨线程访问UI控件 )
WPF:谈谈各种多线程去修改或访问UI线程数据的方法http://www.cnblogs.com/mgen/archive/2012/03/10/2389509.html 子线程非法访问UI线程的数据 ...
- QT GUI(主)线程与子线程之间的通信——使用跨线程的信号槽
在主线程上,可以控制子线程启动,停止,清零 如果子线程启动的话,每一秒钟会向主线程发送一个数字,让主线程更新界面上的数字. 程序截图: 上代码: #include <QtGui> #inc ...
- Android 高级UI设计笔记08:Android开发者常用的7款Android UI组件(转载)
Android开发是目前最热门的移动开发技术之一,随着开发者的不断努力和Android社区的进步,Android开发技术已经日趋成熟,当然,在Android开源社区中也涌现了很多不错的开源UI项目,它 ...
- 套间线程(apartment thread)模型和自由线程(free thread)模型互相创建的情况
- jmeter跨线程组传值和jmeter跨线程组调用
Jmeter的线程组之间是独立的,用Jmeter做接口测试或者是性能测试时,经常会涉及到多个线程组.那么如何将A线程组返回的变量信息提取后,传递给B,C线程组使用呢?这里以已登录接口返回的access ...
- UNIX环境编程学习笔记(28)——多线程编程(三):线程的取消
lienhua342014-11-24 1 取消线程 pthread 提供了pthread_cancel 函数用于请求取消同一进程中的其他线程. #include <pthread.h> ...
- Qt跨线程调用错误解析及解决办法
错误提示:Error: Cannot create children for a parent that is in a different thread. 错误案例分析 新建SerialLink子线 ...
- WPF 精修篇 长时间线程加取消功能
原文:WPF 精修篇 长时间线程加取消功能 <Grid> <Grid.RowDefinitions> <RowDefinition Height="11*&qu ...
随机推荐
- 08查找满足条件的n个数
第一节.寻找和为定值的两个数 题目:输入一个数组和一个数字,在数组中查找两个数,使得它们的和正好是输入的那个数字.要求时间复杂度是O(n).如果有多对数字的和等于输入的数字,输出任意一对即可. 例如输 ...
- vue element 给指数的div加loading
const loading = this.$loading({ lock: true, text: 'Loading', spinner: 'el-icon-lo ...
- css white-space属性
css white-space属性 规定段落中的文本不进行换行
- H3C路由器SSH服务配置命令
- 洛谷 1602 Sramoc问题
Description 话说员工们整理好了筷子之后,就准备将快餐送出了,但是一看订单,都傻眼了:订单上没有留电话号码,只写了一个sramoc(k,m)函数,这什么东西?什么意思?于是餐厅找来了资深顾问 ...
- pip 将 某包指定到某目录 安装
pip 将 某包指定到某目录 安装: pip install --target=d:\somewhere\other\than\the\default package_name
- 【原生JS】进阶最后一个编程篇(与之前的选项卡不同的做法)
完成效果图: <!DOCTYPE html> <html> <head lang="en"> <meta charset="UT ...
- 【codeforces 789B】Masha and geometric depression
[题目链接]:http://codeforces.com/contest/789/problem/B [题意] 让你一个一个地写出等比数列的每一项 (注意是一个一个地写出); 有m个数字不能写; 且数 ...
- H3C 用802.1Q和子接口实现VLAN间路由
- java 菜单
继承体系 MenuBar,Menu,MenuItem之间的关系: 先创建菜单条,再创建菜单,每一个菜单中建立菜单项. 也可以菜单添加到菜单中,作为子菜单. 通过setMenuBar()方法,将菜单添加 ...