ThreadPool(线程池) in .Net

在多线程的程序中,经常会出现两种情况。一种情况下,应用程序中的线程把大部分的时间花费在等待状态,等待某个事件发生,然后才能给予响应;而另外一种情况则是线程平常都处于休眠状态,只是周期性地被唤醒。这里分析及介绍.Net Framework中ThreadPool class来对付第一种情况,相应地也会谈到QueueUserWorkItem方法和WaitCallback委托。而使用Timer(System.Threading.Timer or System.Windows.Forms.Timer)来对付第二种情况,可以参考《System.Threading.Timer类的TimerCallback 委托》open in new window

1. ThreadPool介绍(From MSDN)

ThreadPool class提供了一个线程池,该线程池可用于发送工作项、处理异步 I/O、代表其他线程等待以及处理计时器。线程池允许在后台运行多个工作,而不需要为每个任务频繁地创建和销毁单独的线程,从而减少了开销。

线程池通过为应用程序提供一个由系统管理的辅助线程池使您可以更为有效地使用线程。一个线程监视排到线程池的若干个等待操作的状态。当一个等待操作完成时,线程池中的一个辅助线程就会执行对应的回调函数。

也可以将与等待操作不相关的工作项排列到线程池。若要请求由线程池中的一个线程来处理工作项,请调用 QueueUserWorkItem 方法。此方法将对将被从线程池中选定的线程调用的方法或委托的引用用作参数。一个工作项排入队列后就无法再取消它。

计时器(System.Threading.Timer)队列中的计时器以及已注册的等待操作也使用线程池。它们的回调函数也会排列到线程池。关于这部分的内容,可以参考《System.Threading.Timer类的TimerCallback 委托》。

线程池在首次创建 ThreadPool 类的实例时被创建。线程池具有每个可用处理器 25 个线程的默认限制,这可以使用 mscoree.h 文件中定义的 CorSetMaxThreads 来更改。每个线程使用默认的堆栈大小并按照默认的优先级运行。每个进程只能具有一个操作系统线程池。

2. ThreadPool.QueueUserWorkItem方法

QueueUserWorkItem方法将指定的方法排入队列以便执行,并指定包含该方法所用数据的对象,此方法在有线程池线程变得可用时执行。

public static bool QueueUserWorkItem(
    WaitCallback callBack,
    object state
);

如果将方法成功排入队列,则为 true;否则为 false。如果排入队列的方法仅需要单个数据项,可以将数据项强制转换为类型 Object。如果该方法需要多个复杂数据,则必须定义包含这些数据的类。如果没有参数传入,则可以调用QueueUserWorkItem(WaitCallback callBack)重载。

ThreadPool提供的公共方法都是static方法,因此也不需要生成ThreadPool对象。通过QueueUserWorkItem方法在线程池中添加一个工作项目后,目前没有提供简单的方法取消。你不必建立咨监线程,只需要把相应的函数或方法依托WaitCallback委托传递给ThreadPool.QueueUserWorkItem()方法即可。而线程的创建、管理和运行等等都由系统自动完成,这就是ThreadPool的优点。

3. WaitCallback委托

WaitCallback委托声明线程池要执行的回调方法,回调方法的声明必须与WaitCallback委托声明具有相同的参数。

WaitCallback 表示要在 ThreadPool 线程上执行的回调方法。创建委托,方法是将回调方法传递给 WaitCallback 构造函数。您的方法必须具有此处所显示的签名。通过将 WaitCallback 委托传递给 ThreadPool.QueueUserWorkItem 来将任务排入队列以便执行。您的回调方法将在某个线程池线程可用时执行。

如果要将信息传递给回调方法,请创建包含所需信息的对象,并在将任务排入队列以便执行时将它传递给 QueueUserWorkItem。每次执行您的回调方法时,state 参数都包含此对象。

通过将一个方法打包到 WaitCallback 委托中,然后将该委托传递给 ThreadPool.QueueUserWorkItem 静态方法,在线程池中对任务进行排队。

4. Demo application using ThreadPool class

codeproject.com上有一个不错的Demo: Proper Threading in Winforms .Net, written by Shawn Cicoria,并附有Source code。

在上述Demo程序中,不仅提供了使用ThreadPool线程池的方法,而且还演示了Thread和ThreadStart 委托的方法。

private void button1_Click(object sender, System.EventArgs e)
{
    ShowProgressDelegate showProgress = new ShowProgressDelegate(ShowProgress);
    int imsgs = 100;

    //One Way... Using ThreadPool
    if (cbThreadPool.Checked)
    {
        object obj = new object[] { this, showProgress, imsgs };
        WorkerClass wc = new WorkerClass();
        bool rc = ThreadPool.QueueUserWorkItem(new WaitCallback(wc.RunProcess), obj);
        EnableButton(!rc);
    }
    else
    {
        //another way.. using straight threads
        WorkerClass wc = new WorkerClass(this, showProgress, new object[] { imsgs });
        Thread t = new Thread(new ThreadStart(wc.RunProcess));
        t.IsBackground = true; //make them a daemon - prevent thread callback issues
        t.Start();
        EnableButton(false);
    }
}

另外,上述Demo程序中还调用Windows form控件的BeginInvoke方法:在创建控件的基础句柄所在线程上,用指定的参数异步执行指定委托。BeginInvoke方法在不同的线程池线程上回调指定的委托,实际上就是发起后台的调用来放置一条消息到windows form的消息循环(message loop)中。

public IAsyncResult BeginInvoke(Delegate, object[]);

***

Any questions about the demo application, please leave message below. I will try to explain it. Thanks.

References

1, MSDN, ThreadPool, QueueUserWorkItem and WaitCallback

2, Shawn Cicoria, Proper Threading in Winforms .Net, http://www.codeproject.com/csharp/winformthreading.asp

posted on 2004-11-23 01:41 Rickie 阅读(485) 评论(3)


2004-11-23 23:44 msolap

进一步解释最后一段,通常Delegate的BeginInvoke异步执行都是由线程池来执行的。由于窗口代码不能由线程池执行,所以 System.Windows.Forms下的Control类特别提供了一个BeginInvoke,以达到异步执行的效果,且由创建窗口的线程执行(这就是为什么要放一条消息,而不是直接用ThreadPool的Queue...方法。)

2004-11-24 02:25 Rickie

Good. Thanks, msolap.

2004-11-30 21:10 Flier Lu

CLR自带的这个线程池支持巨弱,而且还是静态实现,真不知BCL team怎么想的。网上有介绍通过IOCP实现线程池的,如果不考虑win9x兼容性则是一种非常好的解决方法。配合异步socket之类技术,可以弄出效率高而且结构优雅的网络服务器解决方案。我正在整一个这类参考实现,看看周末有时间弄篇文章出来讨论。

Contributors: FHL