一、基于事件的异步模型(Event-Based Asynchronous EBA)
EAP提供了一个简单的方式来使用多线程编程,提供了多线程能力的同时而不用显式的打开或者管理线程。同时提供了一个取消模型,可以安全的更新WPF和WindowsFormsControl的属性。
EAP只是一个模型,这些特性必须要采用实际的代码来实现。在.NET Framework中最明显的就是BackgroundWorker类、WebClient类等。这些类中的方法以*Async结尾的犯法就是异步执行的:它们新打开一个线程,将结果返回给调用的方法,当执行完毕之后调用以*Completed结尾的方法来处理,这个方法会自动调用Invoke方法(WPF和WindowForms中)。
二、BackgroundWorker类
这是一个System.ComponentModel命名空间中的辅助类,这是一个一般意义上EAP模型的实现。这个类使用了线程池,因此不能调用Abort方法。
1、使用BackgroundWorker
- 首先实例化一个BackgroundWorker类的对象,并且调用DoWork方法
- 调用RunWorkerAsync方法,可以提供一个对象类型的参数
static BackgroundWorker _bw = new BackgroundWorker(); static void Main() { _bw.DoWork += bw_DoWork; _bw.RunWorkerAsync ("Message to worker"); Console.ReadLine(); } static void bw_DoWork (object sender, DoWorkEventArgs e) { // This is called on the worker thread Console.WriteLine (e.Argument); // writes "Message to worker" // Perform time-consuming task... }RunWorkerAsync方法将参数总是传递给DoWork方法。同时在DoWork方法执行完毕之后,有一个RunWorkerCompleted事件,来处理执行完毕之后的动作。
-
支持进度报告:需要设置
WorkerReportsProgress
属性为true,同时周期性的在DoWork中调用ReportProgress
方法,然后还要处理ProgressChanged
事件,通过它的事件参数的属性ProgressPercentage来实现。ProgressChanged事件中的代码可以*的与UI控件进行交互,对更新进度条尤为有用。 - 支持退出报告:设置WorkerSupportsCancellation属性为true;在DoWork中周期地检查CancellationPending属性:如果为true,就设置事件参数的Cancel属性为true,然后返回。(工作线程可能会设置Cancel为true,并且不通过CancellationPending进行提示——如果判定工作太过困难并且它不能继续运行);调用CancelAsync来请求退出
2、使用BackgroundWorker实现子类
三、定时器
周期性的执行某个方法最简单的方法就是使用一个计时器,比如System.Threading 命名空间下Timer类。线程计时器利用了线程池,允许多个计时器被创建而没有额外的线程开销。 Timer 算是相当简易的类,它有一个构造器和两个方法(这对于极简主义者来说是最高兴不过的了)。
public sealed class Timer : MarshalByRefObject, IDisposable { public Timer (TimerCallback tick, object state, 1st, subsequent); public bool Change (1st, subsequent); // To change the interval public void Dispose(); // To kill the timer } 1st = time to the first tick in milliseconds or a TimeSpan subsequent = subsequent intervals in milliseconds or a TimeSpan (use Timeout.Infinite for a one-off callback)
接下来这个例子,计时器5秒钟之后调用了Tick 的方法,它写"tick...",然后每秒写一个,直到用户敲 Enter:
using System; using System.Threading; class Program { static void Main() { Timer tmr = new Timer (Tick, "tick...", 5000, 1000); Console.ReadLine(); tmr.Dispose(); // End the timer } static void Tick (object data) { // This runs on a pooled thread Console.WriteLine (data); // Writes "tick..." } }
.NET framework在System.Timers命名空间下提供了另一个计时器类。它完全包装自System.Threading.Timer,在使用相同的线程池时提供了额外的便利——相同的底层引擎。下面是增加的特性的摘要:
- 实现了Component,允许它被放置到Visual Studio设计器中
- Interval属性代替了Change方法
- Elapsed 事件代替了callback委托
- Enabled属性开始或暂停计时器
- 提够Start 和 Stop方法,万一对Enabled感到迷惑
- AutoReset标志来指示是否循环(默认为true)
例子:
using System; using System.Timers; // Timers namespace rather than Threading class SystemTimer { static void Main() { Timer tmr = new Timer(); // Doesn‘t require any args tmr.Interval = 500; tmr.Elapsed += tmr_Elapsed; // Uses an event instead of a delegate tmr.Start(); // Start the timer Console.ReadLine(); tmr.Stop(); // Pause the timer Console.ReadLine(); tmr.Start(); // Resume the timer Console.ReadLine(); tmr.Dispose(); // Permanently stop the timer } static void tmr_Elapsed (object sender, EventArgs e) { Console.WriteLine ("Tick"); } }
.NET framework 还提供了第三个计时器——在System.Windows.Forms 命名空间下。虽然类似于System.Timers.Timer 的接口,但功能特性上有根本的不同。一个Windows Forms 计时器不能使用线程池,代替为总是在最初创建它的线程上触发 "Tick"事件。假定这是主线程——负责实例化所有Windows Forms程序中的forms和控件,计时器的事件能够操作forms和控件而不违反线程安全——或者强加单元线程模式。Control.Invoke是不需要的。它实质上是一个单线程timer
Windows Forms计时器必须迅速地执行来更新用户接口。迅速地执行是非常重要的,因为Tick事件被主线程调用,如果它有停顿, 将使用户接口变的没有响应。
四、 局部存储
每个线程与其它线程数据存储是隔离的,这对于“不相干的区域”的存储是有益的,它支持执行路径的基础结构,如通信,事务和安全令牌。 通过方法参数传递这些数据是十分笨拙的。存储这些数据到静态域意味着这些数据可以被所有线程共享。
Thread.GetData从一个线程的隔离数据中读,Thread.SetData 写入数据。 两个方法需要一个LocalDataStoreSlot对象来识别内存槽——这包装自一个内存槽的名称的字符串,这个名称 你可以跨所有的线程使用,它们将得到不各自的值,看这个例子:
class ... {
// 相同的LocalDataStoreSlot 对象可以用于跨所有线程
LocalDataStoreSlot secSlot = Thread.GetNamedDataSlot ("securityLevel");
// 这个属性每个线程有不同的值
int SecurityLevel {
get {
object data = Thread.GetData (secSlot);
return data == null ? 0 : (int) data; // null == 未初始化
}
set {
Thread.SetData (secSlot, value);
}
}
Thread.FreeNamedDataSlot将释放给定的数据槽,它跨所有的线程——但只有一次,当所有相同名字LocalDataStoreSlot对象作为垃圾被回收时退出作用域时发生。这确保了线程不得到数据槽从它们的脚底下撤出——也保持了引用适当的使用之中的LocalDataStoreSlot对象。