.NET多线程编程:多任务和多线程

在.NET多线程编程这个系列我们讲一起来探讨多线程编程的各个方面。首先我将在本篇文章的开始向大家介绍多线程的有关概念以及多线程编程的基础知识;在接下来的文章中,我将逐一讲述。NET平台上多线程编程的知识,诸如System.Threading命名空间的重要类以及方法,并就一些例子程序来作说明。

 

引言

 

早期的计算硬件十分复杂,但是操作系统执行的功能确十分的简单。那个时候的操作系统在任一时间点只能执行一个任务,也就是同一时间只能执行一个程序。多个任务的执行必须得轮流执行,在系统里面进行排队等候。由于计算机的发展,要求系统功能越来越强大,这个时候出现了分时操作的概念:每个运行的程序占有一定的处理机时间,当这个占有时间结束后,在等待队列等待处理器资源的下一个程序就开始投入运行。注意这里的程序在占有一定的处理器时间后并没有运行完毕,可能需要再一次或多次分配处理器时间。那么从这里可以看出,这样的执行方式显然是多个程序的并行执行,但是在宏观上,我们感觉到多个任务是同时执行的,因此多任务的概念就诞生了。每个运行的程序都有自己的内存空间,自己的堆栈和环境变量设置。每一个程序对应一个进程,代表着执行一个大的任务。一个进程可以启动另外一个进程,这个被启动的进程称为子进程。父进程和子进程的执行只有逻辑上的先后关系,并没有其他的关系,也就是说他们的执行是独立的。但是,可能一个大的程序(代表着一个大的任务),可以分割成很多的小任务,为了功能上的需要也有可能是为了加快运行的速度,可能需要同一时间执行多个任务(每个任务分配一个多线程来执行相应的任务)。举个例子来说,你正在通过你的web浏览器查看一些精彩的文章,你需要把好的文章给下载下来,可能有些非常精彩的文章你需要收藏起来,你就用你的打印机打印这些在线的文章。在这里,浏览器一边下载HTML格式的文章,一边还要打印文章。这就是一个程序同时执行多个任务,每个任务分配一个线程来完成。因此我们可以看出一个程序同时执行多个任务的能力是通过多线程来实现的。

 

多线程VS多任务

 

正如上面所说的,多任务是相对与操作系统而言,指的是同一时间执行多个程序的能力,虽然这么说,但是实际上在只有一个CPU的条件下不可能同时执行两个以上的程序。CPU在程序之间做高速的切换,使得所有的程序在很短的时间之内可以得到更小的CPU时间,这样从用户的角度来看就好象是同时在执行多个程序。多线程相对于操作系统而言,指的是可以同时执行同一个程序的不同部分的能力,每个执行的部分被成为线程。所以在编写应用程序时,我们必须得很好的设计以 避免不同的线程执行时的相互干扰。这样有助于我们设计健壮的程序,使得我们可以在随时需要的时候添加线程。

 

线程的概念

 

线程可以被描述为一个微进程,它拥有起点,执行的顺序系列和一个终点。它负责维护自己的堆栈,这些堆栈用于异常处理,优先级调度和其他一些系统重新恢复线程执行时需要的信息。从这个概念看来,好像线程与进程没有任何的区别,实际上线程与进程是肯定有区别的:

一个完整的进程拥有自己独立的内存空间和数据,但是同一个进程内的线程是共享内存空间和数据的。一个进程对应着一段程序,它是由一些在同一个程序里面独立的同时的运行的线程组成的。线程有时也被称为并行运行在程序里的轻量级进程,线程被称为是轻量级进程是因为它的运行依赖与进程提供的上下文环境,并且使用的是进程的资源。

在一个进程里,线程的调度有抢占式或者非抢占的模式。

在抢占模式下,操作系统负责分配CPU时间给各个进程,一旦当前的进程使用完分配给自己的CPU时间,操作系统将决定下一个占用CPU时间的是哪一个线程。因此操作系统将定期的中断当前正在执行的线程,将CPU分配给在等待队列的下一个线程。所以任何一个线程都不能独占CPU。每个线程占用CPU的时间取决于进程和操作系统。进程分配给每个线程的时间很短,以至于我们感觉所有的线程是同时执行的。实际上,系统运行每个进程的时间有2毫秒,然后调度其他的线程。它同时他维持着所有的线程和循环,分配很少量的CPU时间给线程。 线程的的切换和调度是如此之快,以至于感觉是所有的线程是同步执行的。

 

调度是什么意思?调度意味着处理器存储着将要执行完CPU时间的进程的状态和将来某个时间装载这个进程的状态而恢复其运行。然而这种方式也有不足之处,一个线程可以在任何给定的时间中断另外一个线程的执行。假设一个线程正在向一个文件做写操作,而另外一个线程中断其运行,也向同一个文件做写操作。 Windows 95/NT, UNIX使用的就是这种线程调度方式。

在非抢占的调度模式下,每个线程可以需要CPU多少时间就占用CPU多少时间。在这种调度方式下,可能一个执行时间很长的线程使得其他所有需要CPU的线程”饿死”。在处理机空闲,即该进程没有使用CPU时,系统可以允许其他的进程暂时使用CPU。占用CPU的线程拥有对CPU的控制权,只有它自己主动释放CPU时,其他的线程才可以使用CPU。一些I/O和Windows 3。x就是使用这种调度策略。

在有些操作系统里面,这两种调度策略都会用到。非抢占的调度策略在线程运行优先级一般时用到,而对于高优先级的线程调度则多采用抢占式的调度策略。如果你不确定系统采用的是那种调度策略,假设抢占的调度策略不可用是比较安全的。在设计应用程序的时候,我们认为那些占用CPU时间比较多的线程在一定的间隔是会释放CPU的控制权的,这时候系统会查看那些在等待队列里面的与当前运行的线程同一优先级或者更高的优先级的线程,而让这些线程得以使用CPU。如果系统找到一个这样的线程,就立即暂停当前执行的线程和激活满足条件的线程。如果没有找到同一优先级或更高级的线程,当前线程还继续占有CPU。当正在执行的线程想释放CPU的控制权给一个低优先级的线程,当前线程就转入睡眠状态而让低优先级的线程占有CPU。

在多处理器系统,操作系统会将这些独立的线程分配给不同的处理器执行,这样将会大大的加快程序的运行。线程执行的效率也会得到很大的提高,因为将线程的分时共享单处理器变成了分布式的多处理器执行。这种多处理器在三维建模和图形处理是非常有用的。

 

需要多线程吗

 

我们发出了一个打印的命令,要求打印机进行打印任务,假设这时候计算机停止了响应而打印机还在工作,那岂不是我们的停止手上的事情就等着这慢速的打印机打印?所幸的是,这种情况不会发生,我们在打印机工作的时候还可以同时听音乐或者画图。因为我们使用了独立的多线程来执行这些任务。你可能会对多个用户同时访问数据库或者web服务器感到吃惊,他们是怎么工作的?这是因为为每个连接到数据库或者web服务器的用户建立了独立的线程来维护用户的状态。如果一个程序的运行有一定的顺序,这时候采用这种方式可能会出现问题,甚至导致整个程序崩溃。如果程序可以分成独立的不同的任务,使用多线程,即使某一部分任务失败了,对其他的也没有影响,不会导致整个程序崩溃。

 

毫无疑问的是,编写多线程程序使得你有了一个利器可以驾奴非多线程的程序,但是多线程也可能成为一个负担或者需要不小的代价。如果使用的不当,会带来更多的坏处。如果一个程序有很多的线程,那么其他程序的线程必然只能占用更少的CPU时间;而且大量的CPU时间是用于线程调度的;操作系统也需要足够的内存空间来维护每个线程的上下文信息;因此,大量的线程会降低系统的运行效率。因此,如果使用多线程的话,程序的多线程必须设计的很好,否则带来的好处将远小于坏处。因此使用多线程我们必须小心的处理这些线程的创建,调度和释放工作。

 

多线程程序设计提示

 

有多种方法可以设计多线程的应用程序。正如后面的文章所示,我将给出详细的编程示例,通过这些例子,你将可以更好的理解多线程。线程可以有不同的优先级,举例子来说,在我们的应用程序里面,绘制图形或者做大量运算的同时要接受用户的输入,显然用户的输入需要得到第一时间的响应,而图形绘制或者运算则需要大量的时间,暂停一下问题不大,因此用户输入线程将需要高的悠闲级,而图形绘制或者运算低优先级即可。这些线程之间相互独立,相互不影响。

在上面的例子中,图形绘制或者大量的运算显然是需要站用很多的CPU时间的,在这段时间,用户没有必要等着他们执行完毕再输入信息,因此我们将程序设计成独立的两个线程,一个负责用户的输入,一个负责处理那些耗时很长的任务。这将使得程序更加灵活,能够快速响应。同时也可以使得用户在运行的任何时候取消任务的可能。在这个绘制图形的例子中,程序应该始终负责接收系统发来的消息。如果由于程序忙于一个任务,有可能会导致屏幕变成空白,这显然需要我们的程序来处理这样的事件。所以我必须得有一个线程负责来处理这些消息,正如刚才所说的应该触发重画屏幕的工作。

我们应该把握一个原则,对于那些对时间要求比较紧迫需要立即得到相应的任务,我们因该给予高的优先级,而其他的线程优先级应该低于她的优先级。侦听客户端请求的线程应该始终是高的优先级,对于一个与用户交互的用户界面的任务来说,它需要得到第一时间的响应,其优先级因该高优先级。

使用类、类的方法或类的属性都可以向线程传递参数:
public
class UrlDownloader{ string url; public UrlDownloader (string url) { this.url = url; } public void Download() {
WebClient wc = new WebClient();
Console.WriteLine("Downloading " + url); byte[] buffer = wc.DownloadData (url); string download = Encoding.ASCII.GetString(buffer); Console.WriteLine(download); Console.WriteLine("Download successful.");
//这里你可以将download进行保存等处理...... }}[... 在另一个类中使用它们...] UrlDownloader downloader = new UrlDownloader (yourUrl);new Thread (new ThreadStart (downloader.Download)).Start();
注意参数是如何传递的。
在.NET 2.0中还可以这样:
(CODE-BESIDE)
方式一:
ThreadStart starter = delegate { Download(yourUrl); });new Thread(starter).Start();
//使用线程池
WaitCallback callback = delegate (object state) { Download ((string)state); };ThreadPool.QueueUserWorkItem (callback, yourUrl);
方式二(使用ParameterizedThreadStart):
Thread t = new Thread (new ParameterizedThreadStart(DownloadUrl));t.Start (myUrl);static void DownloadUrl(object url){    // ....
}
看一下以下两个例子的运行结果:
//TestThread.cs
using
System;using System.Threading;public class Test{ static int count=0; static void Main() { ThreadStart job = new ThreadStart(ThreadJob); Thread thread = new Thread(job); thread.Start(); for (int i=0; i < 5; i++) { count++; } thread.Join(); Console.WriteLine ("Final count: {0}", count); } static void ThreadJob() { for (int i=0; i < 5; i++) { count++; } }}
//InnerDataThread.cs
using System;using System.Threading;public class Test{    static int count=0;        static void Main()    {        ThreadStart job = new ThreadStart(ThreadJob);        Thread thread = new Thread(job);        thread.Start();                for (int i=0; i < 5; i++)        {            int tmp = count;            Console.WriteLine ("Read count={0}", tmp);            Thread.Sleep(50);            tmp++;            Console.WriteLine ("Incremented tmp to {0}", tmp);            Thread.Sleep(20);            count = tmp;            Console.WriteLine ("Written count={0}", tmp);            Thread.Sleep(30);        }                thread.Join();        Console.WriteLine ("Final count: {0}", count);    }        static void ThreadJob()    {        for (int i=0; i < 5; i++)        {            int tmp = count;            Console.WriteLine ("\t\t\t\tRead count={0}", tmp);            Thread.Sleep(20);            tmp++;            Console.WriteLine ("\t\t\t\tIncremented tmp to {0}", tmp);            Thread.Sleep(10);            count = tmp;            Console.WriteLine ("\t\t\t\tWritten count={0}", tmp);            Thread.Sleep(40);        }    }}
Read count=0                                Read count=0                                Incremented tmp to 1                                Written count=1Incremented tmp to 1Written count=1                                Read count=1                                Incremented tmp to 2Read count=1                                Written count=2                                Read count=2Incremented tmp to 2                                Incremented tmp to 3Written count=2                                Written count=3Read count=3                                Read count=3Incremented tmp to 4                                Incremented tmp to 4                                Written count=4Written count=4                                Read count=4Read count=4                                Incremented tmp to 5                                Written count=5Incremented tmp to 5Written count=5Read count=5Incremented tmp to 6Written count=6Final count: 6

再比较下面这个例子:

//使用Monitor.Enter/Exit
//MonitorThread.cs

using System;
using System.Threading;

public class Test
{
    static int count=0;
    static readonly object countLock = new object();
   
    static void Main()
    {
        ThreadStart job = new ThreadStart(ThreadJob);
        Thread thread = new Thread(job);
        thread.Start();
       
        for (int i=0; i < 5; i++)
        {
            Monitor.Enter(countLock);
            int tmp = count;
            Console.WriteLine ("Read count={0}", tmp);
            Thread.Sleep(50);
            tmp++;
            Console.WriteLine ("Incremented tmp to {0}", tmp);
            Thread.Sleep(20);
            count = tmp;
            Console.WriteLine ("Written count={0}", tmp);
            Monitor.Exit(countLock);
            Thread.Sleep(30);
        }
       
        thread.Join();
        Console.WriteLine ("Final count: {0}", count);
    }
   
    static void ThreadJob()
    {
        for (int i=0; i < 5; i++)
        {
            Monitor.Enter(countLock);
            int tmp = count;
            Console.WriteLine ("\t\t\t\tRead count={0}", tmp);
            Thread.Sleep(20);
            tmp++;
            Console.WriteLine ("\t\t\t\tIncremented tmp to {0}", tmp);
            Thread.Sleep(10);
            count = tmp;
            Console.WriteLine ("\t\t\t\tWritten count={0}", tmp);
            Monitor.Exit(countLock);
            Thread.Sleep(40);
        }
    }
}

结果与上例InnerDataThread.cs是不一样的,原因就在于Monitor的使用了。

Read count=0Incremented tmp to 1Written count=1                                Read count=1                                Incremented tmp to 2                                Written count=2Read count=2Incremented tmp to 3Written count=3                                Read count=3                                Incremented tmp to 4                                Written count=4Read count=4Incremented tmp to 5Written count=5                                Read count=5                                Incremented tmp to 6                                Written count=6Read count=6Incremented tmp to 7Written count=7                                Read count=7                                Incremented tmp to 8                                Written count=8Read count=8Incremented tmp to 9Written count=9                                Read count=9                                Incremented tmp to 10                                Written count=10Final count: 10

下面使用lock来锁定线程:
// LockThread.cs
using System;
using System.Threading;

public class Test
{
    static int count=0;
    static readonly object countLock = new object();
   
    static void Main()
    {
        ThreadStart job = new ThreadStart(ThreadJob);
        Thread thread = new Thread(job);
        thread.Start();
       
        for (int i=0; i < 5; i++)
        {
            lock (countLock)
            {
                int tmp = count;
                Console.WriteLine ("Read count={0}", tmp);
                Thread.Sleep(50);
                tmp++;
                Console.WriteLine ("Incremented tmp to {0}", tmp);
                Thread.Sleep(20);
                count = tmp;
                Console.WriteLine ("Written count={0}", tmp);
            }
            Thread.Sleep(30);
        }
       
        thread.Join();
        Console.WriteLine ("Final count: {0}", count);
    }
   
    static void ThreadJob()
    {
        for (int i=0; i < 5; i++)
        {
            lock (countLock)
            {
                int tmp = count;
                Console.WriteLine ("\t\t\t\tRead count={0}", tmp);
                Thread.Sleep(20);
                tmp++;
                Console.WriteLine ("\t\t\t\tIncremented tmp to {0}", tmp);
                if (count < 100)
                    throw new Exception();
                Thread.Sleep(10);
                count = tmp;
                Console.WriteLine ("\t\t\t\tWritten count={0}", tmp);
            }
            Thread.Sleep(40);
        }
    }
}

结果如何?与MonitorThread.cs比较一下,再想想看。

// DeadLockSample.cs
// 分析一下为什么会发生死锁?

using System;using System.Threading;public class Test{    static readonly object firstLock = new object();    static readonly object secondLock = new object();        static void Main()    {        new Thread(new ThreadStart(ThreadJob)).Start();                // Wait until we're fairly sure the other thread        // has grabbed firstLock        Thread.Sleep(500);                Console.WriteLine ("Locking secondLock");        lock (secondLock)        {            Console.WriteLine ("Locked secondLock");            Console.WriteLine ("Locking firstLock");            lock (firstLock)            {                Console.WriteLine ("Locked firstLock");            }            Console.WriteLine ("Released firstLock");        }        Console.WriteLine("Released secondLock");    }        static void ThreadJob()    {        Console.WriteLine ("\t\t\t\tLocking firstLock");        lock (firstLock)        {            Console.WriteLine("\t\t\t\tLocked firstLock");            // Wait until we're fairly sure the first thread            // has grabbed secondLock            Thread.Sleep(1000);            Console.WriteLine("\t\t\t\tLocking secondLock");            lock (secondLock)            {                Console.WriteLine("\t\t\t\tLocked secondLock");            }            Console.WriteLine ("\t\t\t\tReleased secondLock");        }        Console.WriteLine("\t\t\t\tReleased firstLock");    }}
Locking firstLock
Locked firstLock
Locking secondLock
Locked secondLock
Locking firstLock Locking secondLock

因应之道,使用Queue和Monitor:

//QueueMonitorThread.cs

using System;using System.Collections;using System.Threading;public class Test{    static ProducerConsumer queue;        static void Main()    {        queue = new ProducerConsumer();        new Thread(new ThreadStart(ConsumerJob)).Start();                Random rng = new Random(0);        for (int i=0; i < 10; i++)        {            Console.WriteLine ("Producing {0}", i);            queue.Produce(i);            Thread.Sleep(rng.Next(1000));        }    }        static void ConsumerJob()    {        // Make sure we get a different random seed from the        // first thread        Random rng = new Random(1);        // We happen to know we've only got 10         // items to receive        for (int i=0; i < 10; i++)        {            object o = queue.Consume();            Console.WriteLine ("\t\t\t\tConsuming {0}", o);            Thread.Sleep(rng.Next(1000));        }    }}public class ProducerConsumer{    readonly object listLock = new object();    Queue queue = new Queue();    public void Produce(object o)    {        lock (listLock)        {            queue.Enqueue(o);            if (queue.Count==1)            {                Monitor.Pulse(listLock);            }        }    }        public object Consume()    {        lock (listLock)        {            while (queue.Count==0)            {                Monitor.Wait(listLock);            }            return queue.Dequeue();        }    }}
Producing 0 Consuming 0
Producing 1 Consuming 1
Producing 2 Consuming 2
Producing 3 Consuming 3
Producing 4
Producing 5 Consuming 4
Producing 6 Consuming 5
                                         Consuming 6
Producing 7 Consuming 7
Producing 8 Consuming 8
Producing 9 Consuming 9
处理周期事件
1、System.WinForms.Timer
Timer的Tick事件代码:
Interlocked.Increment(ref _count);

2、ThreadPool
A.生成WaitOrTimerCallback事例
B.生成一个同步对象
C.添加到线程池

例1:
/*RegisterWaitForSingleObject
下面的示例演示了几种线程处理功能。

使用 RegisterWaitForSingleObject 将需要执行的任务以 ThreadPool 线程的方式排队。
使用 AutoResetEvent 发出信号,通知执行任务。
用 WaitOrTimerCallback 委托处理超时和信号。
用 RegisteredWaitHandle 取消排入队列的任务。
*/
using System;
using System.Threading;

// TaskInfo contains data that will be passed to the callback
// method.
public class TaskInfo {
    public RegisteredWaitHandle Handle = null;
    public string OtherInfo = "default";
}

public class Example {
    public static void Main(string[] args) {
        // The main thread uses AutoResetEvent to signal the
        // registered wait handle, which executes the callback
        // method.
        AutoResetEvent ev = new AutoResetEvent(false);

        TaskInfo ti = new TaskInfo();
        ti.OtherInfo = "First task";
        // The TaskInfo for the task includes the registered wait
        // handle returned by RegisterWaitForSingleObject.  This
        // allows the wait to be terminated when the object has
        // been signaled once (see WaitProc).
        ti.Handle = ThreadPool.RegisterWaitForSingleObject(
            ev,
            new WaitOrTimerCallback(WaitProc),
            ti,
            100,
            false
        );

        // The main thread waits three seconds, to demonstrate the
        // time-outs on the queued thread, and then signals.
        Thread.Sleep(3100);
        Console.WriteLine("Main thread signals.");
        ev.Set();

        // The main thread sleeps, which should give the callback
        // method time to execute.  If you comment out this line, the
        // program usually ends before the ThreadPool thread can execute.
        Thread.Sleep(1000);
        // If you start a thread yourself, you can wait for it to end
        // by calling Thread.Join.  This option is not available with
        // thread pool threads.
    }
  
    // The callback method executes when the registered wait times out,
    // or when the WaitHandle (in this case AutoResetEvent) is signaled.
    // WaitProc unregisters the WaitHandle the first time the event is
    // signaled.
    public static void WaitProc(object state, bool timedOut) {
        // The state object must be cast to the correct type, because the
        // signature of the WaitOrTimerCallback delegate specifies type
        // Object.
        TaskInfo ti = (TaskInfo) state;

        string cause = "TIMED OUT";
        if (!timedOut) {
            cause = "SIGNALED";
            // If the callback method executes because the WaitHandle is
            // signaled, stop future execution of the callback method
            // by unregistering the WaitHandle.
            if (ti.Handle != null)
                ti.Handle.Unregister(null);
        }

        Console.WriteLine("WaitProc( {0} ) executes on thread {1}; cause = {2}.",
            ti.OtherInfo,
            Thread.CurrentThread.GetHashCode().ToString(),
            cause
        );
    }
}

例2:
using System;
using System.Threading;

class State
{
 private int nCalledTimes = 0;
 public void Called()
 {
  Interlocked.Increment(ref nCalledTimes);
 }
 public int CalledTimes
 {
  get
  {
   return nCalledTimes;
  }
 }
}

class App
{
 static public void PeriodicMethod(object state , bool timeOut)
 {
  // timeOut为false时,说明等待有效,否则超时
  Console.WriteLine("\nThread {0}开始处理定时事件",Thread.CurrentThread.GetHashCode());
  if(!timeOut)
   Console.WriteLine("获得等待信号");
  else
   Console.WriteLine("超时事件发生");
  
  if(state!=null)
  {
   ((State)state).Called();
   Console.WriteLine("调用了{0}次",((State)state).CalledTimes);
  }
  Thread.Sleep(100);
  Console.WriteLine("Thread {0}处理定时事件完毕\n",Thread.CurrentThread.GetHashCode());
 }
 
 static public void Main()
 {
  AutoResetEvent myEvent = new AutoResetEvent(false);
  WaitOrTimerCallback waitOrTimerCallback = new WaitOrTimerCallback(App.PeriodicMethod);
  int timeout = 1000;
  bool executeOnlyOnce = false;
  
  State state = new State();
  
  ThreadPool.RegisterWaitForSingleObject(myEvent , waitOrTimerCallback , state ,timeout,executeOnlyOnce);
  //Thread.Sleep(10000);
  myEvent.Set();
  Console.WriteLine("按任意键退出");
  Console.ReadLine();
 }
}

3、System.Threading.Timer
A.实例化一个TimerCallback代理callback
B.创建一个System.Threading.Timer实例timer
C.如果有必要,调用 timer.Change重新设置timer的durTime和period
D.用timer.Dispose释放timer
using System;
using System.Threading;

class State
{
 private int threadID = -1;
 private AutoResetEvent firstTimerFired = null;
 public State(int threadID , AutoResetEvent firstTimerFired)
 {
  this.threadID = threadID;
  this.firstTimerFired = firstTimerFired;
 }
 
 public void Show()
 {
  Console.WriteLine("thread.HashCode={0}\tthreadID={1}在工作",Thread.CurrentThread.GetHashCode(),threadID);
 }
 
 public AutoResetEvent FirstTimerFired
 {
  get
  {
   return firstTimerFired;
  }
  set
  {
   firstTimerFired = value;
  }
 }
}

class App
{
 public static void Main()
 {
  Console.WriteLine("每2秒执行一次时钟事件");
  TimerCallback callback = new TimerCallback(App.CheckStatus);
  Timer timer1 = new Timer(callback , null ,1000 ,2000);
  AutoResetEvent firstTimerFired = new AutoResetEvent(false);
  State state = new State(2,firstTimerFired);
  
  Timer timer2 = new Timer(callback ,state , 5000 ,0);//定时器事件只触发一次,period为0
  firstTimerFired.WaitOne();
  
  Console.WriteLine("按回车继续...");
  Console.ReadLine();
  
  timer2.Change(2000,1000);
  Console.WriteLine("按回车继续...");
  Console.ReadLine();
  
  timer1.Dispose();
  timer2.Dispose();
 }
 
 static void CheckStatus(object state)
 {
  if (state !=null)
  {
   ((State)state).Show();
   if(((State)state).FirstTimerFired != null)
    ((State)state).FirstTimerFired.Set();
  }
  else
  {
   Console.WriteLine("tread.HashCode = {0}\tthreadID={1}在工作",Thread.CurrentThread.GetHashCode(),-1);
  }
 }
}

4、System.Timers.Timer
基于服务器的计时器的关键编程元素
Timer 组件引发一个名为 Timer.Elapsed 的事件。您可以为这个事件创建处理程序来执行处理要发生的一切。

Timer 组件的一些更重要的属性和方法还包含:

Interval 属性用来设置引发事件的时间范围,以毫秒计。例如,值为 1000 的时间间隔将一秒钟引发一次事件。
AutoReset 属性决定在给定时间间隔过去之后计时器是否继续引发事件。如果设置成 true,计时器继续重新计算时间间隔并引发事件。如果为 false,它在时间间隔过去后只引发一次事件,然后停止。
Start 方法将计时器的 Enabled 属性设置为 true,它允许计时器开始引发事件。如果计时器已经是启用状态,则调用 Start 方法将重置该计时器。
Stop 方法将计时器的 Enabled 属性设置成 false,以防止计时器再引发事件。
A.创建System.Timers.Timer对象kicker
B.设置周期
C.设置AutoReset为true
D.设置kicker的Elapsed事件
E.启动kicker
F.如果需要,可以重新设置kicker的Interval属性
G.停止记时器
using System;
using System.Timers;
using System.Threading;

class App
{
 private static DateTime stopTime = new DateTime(2005,4,2);
 static void ElapsedHandler(object sender , ElapsedEventArgs e)
 {
  if (DateTime.Compare(e.SignalTime , stopTime) > 0 )
  {
   Console.WriteLine("Thread {0} 处理定事事件",Thread.CurrentThread.GetHashCode());
   Thread.Sleep(100);
  }
 }
 
 static public void Main()
 {
  System.Timers.Timer kicker = new System.Timers.Timer();
  kicker.Interval =1000;
  kicker.AutoReset = true;
  kicker.Elapsed += new ElapsedEventHandler(ElapsedHandler);
  kicker.Start();
  Thread.Sleep(2100);
  Console.WriteLine("改变时间间隔");
  kicker.Interval = 2000;
  Thread.Sleep(2100);
  Console.WriteLine("结束定事器");
  //kicker.Stop();
  stopTime = DateTime.Now;
  Thread.Sleep(2100);
  Console.WriteLine("重新启动定事器");
  kicker.Start();
  Thread.Sleep(8100);
  Console.WriteLine("按任意键退出");
  //Console.ReadLine();
  //Thread.Sleep(14100);
  kicker.Stop();
  stopTime = DateTime.Now;
 }
}

System.Winforms.Timer、System.Threading.Timer、System.Timers.Timer,通过设置定时周期、定时事件、可以启动、终止、再启动定时器、重新设置定时器属性等。功能依次增强。
ThreadPool一旦设置好时钟属性并启动后,就不能对定时器进行控制。
《.net核心技术-原理与架构》

原文地址:https://www.cnblogs.com/Leo_wl/p/1805063.html