\t\t线程池中执行的状态 和 定时器

来源:互联网 发布:投资组合分析软件 编辑:程序博客网 时间:2024/04/28 04:03

线程池中执行的函数

ThreadPool.QueueUserWorkItem 方法运行我们在系统线程池上启动一个函数,它的声明如下:

public static bool QueueUserWorkItem (WaitCallback callBack, object state)
第一个参数指明我们将在池中执行的函数,它的声明必须与WaitCallback代理(delegate)互相匹配:public delegate void WaitCallback (object state);

State 参数允许任何类型的信息传递到该方法中,它在调用QueueUserWorkItem时传入。

让我们结合这些新概念,看看“硬件商店”的另一个实现。

using System;
using System.Threading;
namespace ThreadPoolTest
{
    class MainApp
    {
       static void Main()
       {
          WaitCallback callBack;
          callBack = new WaitCallback(PooledFunc);
          ThreadPool.QueueUserWorkItem(callBack,
             "Is there any screw left?");
          ThreadPool.QueueUserWorkItem(callBack,
             "How much is a 40W bulb?");
          ThreadPool.QueueUserWorkItem(callBack,
             "Decrease stock of monkey wrench");    
         Console.ReadLine();
       }
 
       static void PooledFunc(object state)
       {
          Console.WriteLine("Processing request '{0}'", (string)state);
          // Simulation of processing time
          Thread.Sleep(2000);
          Console.WriteLine("Request processed");
       }
    }
}

为了简化例子,我们在Main 类中创建一个静态方法用于处理请求。由于代理的灵活性,我们可以指定任何实例方法去处理请求,只要这些方法的声明与代理相同。在这里范例中,通过调用Thread.Sleep,实现延迟两秒以模拟处理时间。

你如果编译和执行这个范例,将会看到下面的输出:

Processing request 'Is there any screw left?'
Processing request 'How much is a 40W bulb?'
Processing request 'Decrease stock of monkey wrench'
Request processed
Request processed
Request processed

注意,所有的请求都被不同的线程并行处理了。

我们可以通过在两个方法中加入如下的代码,以此看到更多的信息。

  // Main method
    Console.WriteLine("Main thread. Is pool thread: {0}, Hash: {1}",
             Thread.CurrentThread.IsThreadPoolThread, 
            Thread.CurrentThread.GetHashCode());
    // Pool method
    Console.WriteLine("Processing request '{0}'." + 
      " Is pool thread: {1}, Hash: {2}",
       (string)state, Thread.CurrentThread.IsThreadPoolThread, 
      Thread.CurrentThread.GetHashCode());
 

我们增加了一个Thread.CurrentThread.IsThreadPoolThread的调用。如果目标线程属于线程池,这个属性将返回True。另外,我们还显示了用GetHashCode 方法从当前线程返回的结果。它是唯一标识当前执行线程的值。现在看一看这个输出结果:

Main thread. Is pool thread: False, Hash: 2
Processing request 'Is there any screw left?'. Is pool thread: True, Hash: 4
Processing request 'How much is a 40W bulb?'. Is pool thread: True, Hash: 8
Processing request 'Decrease stock of monkey wrench '. Is pool thread: True, Hash: 9
Request processed
Request processed
Request processed
 

你可以看到所有的请求都被系统线程池中的不同线程执行。再次运行这个例子,注意系统CPU的利用率,如果你没有任何其它应用程序在后台运行的话,它几乎是0%。因为系统唯一正在做的是每执行2秒后就挂起的处理。

         我们来修改一下这个应用,这次我们不挂起处理请求的线程,相反我们会一直让系统忙,为了做到这点,我们用Environment.TickCount. 构建一个每隔两秒就对请求执行一次的循环。

int ticks = Environment.TickCount;
while(Environment.TickCount - ticks < 2000);

现在打开任务管理器,看一看CPU的使用率,你将看到应用程序占有了CPU100%的使用率。再看一下我们程序的输出结果:

Processing request 'Is there any screw left?'. Is pool thread: True, Hash: 7
Processing request 'How much is a 40W bulb?'. Is pool thread: True, Hash: 8
Request processed
Processing request 'Decrease stock of monkey wrench '. Is pool thread: True, Hash: 7
Request processed
Request processed
 

注意第三个请求是在第一个请求处理结束之后执行的,而且线程的号码仍然用原来的7,这个原因是线程池检测到CPU的使用率已经达到100%,一直等待某个线程空闲。它并不会重新创建一个新的线程,这样就会减少线程间的上下文切换开销,以使总体性能更佳。

使用定时器

假如你曾经开发过Microsoft Win32的应用程序,你知道SetTimer函数是API之一,通过这个函数可以指定的一个窗口接收到来自系统时间周期的WM_TIMER消息。用这个方法遇到的第一个问题是你需要一个窗口去接收消息,所以你不能用在控制台应用程序中。另外,基于消息的实现并不是非常精确,假如你的应用程序正在处理其它消息,情况有可能更糟糕。

相对基于Win32的定时器来说, .NET 中一个很重要的改进就是创建不同的线程,该线程阻塞指定的时间,然后通知一个回调函数。这里的定时器不需要Microsoft的消息系统,所以这样就更精确,而且还能用于控制台应用程序中。以下代码显示了这个技术的一种实现:

class MainApp
{
    static void Main()
    {
       MyTimer myTimer = new MyTimer(2000);
       Console.ReadLine();
    }
}
class MyTimer
{
    int m_period;
    public MyTimer(int period)
    {
       Thread thread;
       m_period = period;
       thread = new Thread(new ThreadStart(TimerThread));
       thread.Start();
    }
    void TimerThread()
    {
       Thread.Sleep(m_period);
       OnTimer();
    }
    void OnTimer()
    {
       Console.WriteLine("OnTimer");
    }
}

这个代码一般用于Wn32应用中。每个定时器创建独立的线程,并且等待指定的时间,然后呼叫回调函数。犹如你看到的那样,这个实现的成本会非常高。如果你的应用程序使用了多个定时器,相对的线程数量也会随着使用定时器的数量而增长。

现在我们有.NET 提供的线程池,我们可以从池中改变请求的等待函数,这样就十分有效,而且会提升系统的性能。我们会遇到两个问题:

n          假如线程池已满(所有的线程都在运行中),那么这个请求排到队列中等待,而且定时器不在精确。

n          假如创建了多个定时器,线程池会因为等待它们时间片失效而非常忙。

为了避免这些问题,.NET框架的线程池提供了独立于时间的请求。用了这个函数,我们可以不用任何线程就可以拥有成千上万个定时器,一旦时间片失效,这时,线程池将会处理这些请求。

这些特色出现在两个不同的类中:

         System.Threading.Timer

                   定时器的简单版本,它运行开发人员向线程池中的定期执行的程序指定一个代理(delegate.

System.Timers.Timer

System.Threading.Timer的组件版本,允许开发人员把它拖放到一个窗口表单(form)中,可以把一个事件作为执行的函数。

这非常有助于理解上述两个类与另外一个称为System.Windows.Forms.Timer.的类。这个类只是封装了Win32中消息机制的计数器,如果你不准备开发多线程应用,那么就可以用这个类。

在下面的例子中,我们将用System.Threading.Timer 类,定时器的最简单实现,我们只需要如下定义的构造方法

public Timer(TimerCallback callback,
    object state,
    int dueTime,
    int period);

对于第一个参数(callback),我们可以指定定时执行的函数;第二个参数是传递给函数的通用对象;第三个参数是计时器开始执行前的延时;最后一个参数period,是两个执行之间的毫秒数。

下面的例子创建了两个定时器,timer1timer2

class MainApp
{
    static void Main()
    {
       Timer timer1 = new Timer(new TimerCallback(OnTimer), 1, 0, 2000);
       Timer timer2 = new Timer(new TimerCallback(OnTimer), 2, 0, 3000);
       Console.ReadLine();
    }
    static void OnTimer(object obj)
    {
       Console.WriteLine("Timer: {0} Thread: {1} Is pool thread: {2}", 
         (int)obj,
          Thread.CurrentThread.GetHashCode(),
          Thread.CurrentThread.IsThreadPoolThread);
    }
}

输出:

Timer: 1 Thread: 2 Is pool thread: True
Timer: 2 Thread: 2 Is pool thread: True
Timer: 1 Thread: 2 Is pool thread: True
Timer: 2 Thread: 2 Is pool thread: True
Timer: 1 Thread: 2 Is pool thread: True
Timer: 1 Thread: 2 Is pool thread: True
Timer: 2 Thread: 2 Is pool thread: True

犹如你看到的那样,两个定时器中的所有函数调用都在同一个线程中执行(ID = 2),应用程序使用的资源最小化了。

原创粉丝点击