异步操作(1)-Thread
System.Threading 命名空间
System.Threading在.NET环境下提供了用于多线程编程的类和接口。该名字空间不仅包含了用于同步线程操作的类( Mutex, Monitor, Interlocked, AutoResetEvent, 等), 也包括了ThreadPool 和Timer 类。
异步操作
异步操作主要由两种:受计算限制的异步操作和受I/O限制的异步操作。因此线程分为:工作线程(worker thread)和I/O线程(I/O thread)。
在执行一个受计算限制的异步操作时,可以使用其他线程来执行它。在执行一个受I/O限制的异步操作时,可以让Windows设备驱动程序来做这项工作,执行该操作不需要其他线程的参与。
CLR使用了Windows的线程处理能力,但是CLR保留了与Windows线程分离的权利,也就是说,在某些寄宿情形中,CLR的线程并没有与Windows的线程准确匹配。
线程的系统开销很大,应尽可能的限制线程的使用。创建并销毁一个线程在时间上的开销很大,线程多还会浪费内存资源,而且由于操作系统不得不在可运行线程之间进行调度和上下文切换,从而影响操作系统和应用程序的性能。CLR通过CLR线程池(thread pool)改进了这种现象。线程池可以看做是应用程序自己使用的线程的集合。每个进程都有一个线程池,这个线程池被该进程中的所有应用程序域共享。
当CLR初始化时,线程池中还没有任何线程。线程池维护了一系列操作请求。应用程序希望执行一个异步操作时,可以调用一些方法在线程池的队列中加入一个条目。线程池的代码将从这个队列中提取条目,并将该条目分派到线程池中的线程,如果没有线程则创建一个新的线程(会有相关的性能损失)。当线程池中的线程完成任务时,并不会销毁,而是返回到线程池中,处于空闲状态,等待另外的调用。因为线程不对它自身进行销毁,所以没有性能损失。
线程是启发式的。当请求过多或者空闲线程过多时,线程池就会调整自己线程的数量。他的伟大之处就在于它管理着创建较少的线程以避免浪费资源与创建较多的线程以利用多处理器、超线程处理器、多内核处理器之间的平衡。
永远不要对线程池中的线程数量设置一个上限,因为死锁现象可能会发生。
使用线程池执行受计算限制的异步操作
理想状况下,受计算限制的操作不会执行任何异步I/O操作。一般通过ThreadPool的下述方法实现:QueueUserWorkItem(WaitCallback callback)。参数为一个委托类型,标识了回调函数。还有其他两个版本。示例代码:
using System;
using System.Threading;
public static class Program {
public static void Main() {
Console.WriteLine("Main thread: queuing an asynchronous operation");
ThreadPool.QueueUserWorkItem(ComputeBoundOp, 5);
Console.WriteLine("Main thread: Doing other work here...");
Thread.Sleep(10000); // 模拟其他工作(10 seconds)
Console.WriteLine("Hit <Enter> to end this program...");
Console.ReadLine();
}
// 委托函数
private static void ComputeBoundOp(Object state) {
// 该方法由线程池中的线程执行
Console.WriteLine("In ComputeBoundOp: state={0}", state);
Thread.Sleep(1000);
// 在这个方法返回后,线程就回到线程池中,然后等待下一个任务
}
}
使用专用线程执行受计算限制的异步操作
一般情况下,建议尽量使用线程池中的线程来执行受计算限制的异步操作。当需要执行特定的操作时,我们也可以显式创建一个线程。
采用Threading.Thread类的实例方法可以实现这些操作。示例代码:
using System;
using System.Threading;
public static class Program {
public static void Main() {
Console.WriteLine("Main thread: starting a dedicated thread " +
"to do an asynchronous operation");
Thread dedicatedThread = new Thread(ComputeBoundOp);
dedicatedThread.Start(5);
Console.WriteLine("Main thread: Doing other work here...");
Thread.Sleep(10000); // 模拟其他工作(10 seconds)
dedicatedThread.Join(); // 等待线程终止
Console.WriteLine("Hit <Enter> to end this program...");
Console.ReadLine();
}
// 该方法签名与 ParameterizedThreadStart 委托匹配
private static void ComputeBoundOp(Object state) {
// This method is executed by a dedicated thread
Console.WriteLine("In ComputeBoundOp: state={0}", state);
Thread.Sleep(1000);
// 在这个方法返回后,专用线程就会死亡
}
}
定期执行受计算限制的异步操作
可以通过System.Threading命名空间的Timer类让CLR定期的调用方法。Time的构造器中,callback参数表明了系统线程池中的线程回调的方法,他与imerCallback委托匹配。
关于三个Timer定时器
FCL提供了三个Timer定时器。
1、System.Threading.Timer,希望在另一个线程上定时执行后台任务时,这个定时器是最好的定时器。
2、System.Windows.Forms.Timer。构造该类的实例可以告诉windows将定时器和调用线程相关联。随着定时器的触发,Windows将一个定时器消息(WM_TIMER)插入到线程的消息队列中,调用线程必须执行一个消息泵(message pump),从而提取消息,并将它们分配到期望的回调方法中。所有这些工作都是由一个线程完成的,设置定时器的线程保证时执行回调方法的线程。这就意味着定时器方法不能别多个线程同时调用。
3、System.Timers.Timer。这个定时器基本上是对Threading的Timer类的包装,当定时器时间到期后,将导致CLR将事件加入线程池的队列。此类派生自Sys.ComponentModel的Component类,Component类允许将这些定时器放到VS设计界面上。因此如果希望在设计界面上使用定时器,可以使用这个。一般情况下,还是使用Threading的Timer类。