知识须要不断积累、总结和沉淀,思考和写做是成长的催化剂,输出倒逼输入web
1、线程统一取消模型一、取消令牌二、能够中断的线程一、设计一个中断函数二、建立CancellationTokenSource对象三、启动线程四、取消线程执行2、跨线程访问UI基本方法一、Control.Invoke和BeginInvoke二、桌面退出三、编写线程安全的控件3、BackgroundWorker组件一、干活的代码二、启动任务三、结果取回四、取消任务五、进度报告4、等等数组
线程取消在多线程开发中很是广泛,鉴于此微软在.NET4.0基础类库中引入线程统一取消模型对线程取消功能的支持。安全
线程统一取消模型中两个重要的类型就是CancellationToken和CancellationTokenSource
每一个CancellationTokenSource对象都包容着一个“取消令牌(CancellationToken)”,并经过它的Token属性向外界展露,用法也很简单经过调用CancellationTokenSource的Cancel()方法通知取消令牌。下面有个小栗子多线程
下面介绍一个能够中断的线程模型流程app
一个能够中断的线程函数模板看起来像下面这样异步
public class ThreadFuncObject
{
//经过构造函数从外界传入取消令牌
private CancellationToken _token;
public ThreadFuncObject(CancellationToken token)
{
_token = token;
}
public void DoWork()
{
//各类功能代码...
if (_token.IsCancellationRequested)
{
//完成清理工做...
//简单的处理直接return便可
//return;
//建议抛出个取消异常
throw new OperationCanceledException(_token);
}
//各类功能代码...
}
}
CancellationTokenSource tokenSource = new CancellationTokenSource();
ThreadFuncObject obj = new ThreadFuncObject(tokenSource.Token);
Thread th = new Thread(obj.DoWork);
th.Start();
tokenSource.Cancel();
刚开始桌面程序开发时,在作进度条等通知UI更新的功能时,常常会遇到跨线程访问UI的问题,抛的错就是控件不能从不是建立它的线程去更改。ide
在.NET Framework中,全部的可视化控件都从System.Windows.Forms.Control类派生而来,考虑到跨线程访问控件的须要,Control类提供了相应的方法完成跨线程更新界面。函数
//
// 摘要:
// 在拥有此控件的基础窗口句柄的线程上执行指定的委托。
//
// 参数:
// method:
// 包含要在控件的线程上下文中调用的方法的委托。
//
// 返回结果:
// 正在被调用的委托的返回值,或者若是委托没有返回值,则为 null。
public object Invoke(Delegate method);
Invoke方法的参数是一个委托,表明在建立控件的线程中要执行的方法。实际场景中是要向UI传值的,可使用下面的重载ui
//
// 摘要:
// 在拥有控件的基础窗口句柄的线程上,用指定的自变量列表执行指定委托。
//
// 参数:
// method:
// 一个方法委托,它采用的参数的数量和类型与 args 参数中所包含的相同。
//
// args:
// 做为指定方法的参数传递的对象数组。 若是此方法没有参数,该参数能够是 null。
//
// 返回结果:
// System.Object,它包含正被调用的委托返回值;若是该委托没有返回值,则为 null。
public object Invoke(Delegate method, params object[] args);
使用像下面这样spa
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线程不该兼职过多
桌面(Windows窗体)程序是事件驱动的,应该确保用户不能频繁的点击启动访问控件的线程。由于多线程同时访问同一个控件,可能会形成程序不稳定,出现意想不到的的结果。能够经过控制按钮状态或线程状态,控制线程同步。
若是关闭主窗体致使主线程退出,工做线程还没结束。由于主窗体销毁了,其上的控件都被销毁,而工做线程还包含着访问控件的代码,因此会抛出ObjectDisposedException异常。
前几篇线程中讲到,最简单的方法就是将工做线程设置为后台线程,帮随着主线程的退出而退出,另外一个方法就是在窗体FormClosing关闭事件中,判断工做线程的状态,手动Abort终止它。
咱们能够将多线程访问功能封装进控件里,从而简化编写跨线程访问时的代码。好比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;
}
}
}
}
项目开发中注意封装一些这样的控件,能够简化多线程调用代码,提升项目的开发效率
其实微软提供了一个现成的组件用于跨线程访问UI的,那就是BackgroundWorker组件,大大简化了此类程序的开发。基于事件的异步调用模式,就是经过事件告知何时干什么事。支持报告进度,支持取消。
在DoWork事件中,真正干活的代码放在这里
调用BackgroundWorker组件的RunWorkerAsync方法,此方法会激发DoWork事件。此方法有个重载能够传Object对象,在DoWork中经过DoWorkEventArgs.Argument来接收
BackgroundWorker组件有一个RunWorkerCompleted事件
其中的RunWorkerCompletedEventArgs参数包含如下重要信息:
参数属性 | 描述 |
---|---|
e.Result | 工做任务执行的结果 |
e.Error | 这是一个Exception对象,若是工做任务执行过程当中没有发生异常,则此属性为null,若是发生了异常,此属性引用被抛出的异常对象 |
e.Cancelled | 若是在工做任务完成前用户取消了操做,则此属性为True,不然此属性为False |
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;//仍须要手动提交结束任务
}
//...
}
BackgroundWorker组件有一个ProgressChanged事件。在DoWork事件处理代码中合适的地方调用BackgroundWorker组件的ReportProgress方法,就会激发ProgressChanged事件。ReportProgress除了能够报告进度,也能够经过其重载报告一个object对象,每每是描述信息。
在ProgressChanged事件中使用线程同步上下文作了特殊处理,能够直接访问窗体上控件,无需考虑跨线程问题。
好多东西之前都认真看过,没记性就忘了。立刻要搬家了,装不进脑子里就带不走。每次搬家还要为几本书多付一些搬家费,惆怅。(没有你们电,书本就是最重的东西了)
给我一杯酒再给我一支烟,说走就走有缘就再见