2004年9月4日

感觉非常累

这段时间下来,我累了,真的!非常非常累,感觉时间已经在我心中淡化。唯一没有淡化的就是不停的敲写代码。每当眼一睁开,我就知道,我必须去编程,我必须去完成今天的任务。

我知道,即使完成这个项目。你付出的远远小于公司给你的回报,为什么你要这么卖命,为什么要如此... 不太会说话!

我渐渐的发觉了,有些人变了。可能是日子的改变让他们变的强壮,以至于抵抗力100%。他们那种什么都知道的态度我也感觉到了。我也发觉做项目经理应该好好分配时间,分配任务,而不是去考虑太多的技术,那样你会被一些过多的因素困扰而不能有所成就。

java里面有句很好的话,可惜我不太记得了
大体是这样吧 不以方便而迷大志,不费方便而绝后尘

posted @ 2004-09-04 08:22 停留.Xp 阅读(498) 评论(0) 编辑

2004年8月30日

ASP.NET Framework深度历险(3)

ASP.NET Framework深度历险(3)


March 25,2004
Author:uestc95
ArticleType:原创
E-mail:uestc95@263.net
.NET Framework Version:1.0.3705正式版
VS.NET(C#) Version:7.0.9466正式版



    这几天胃口还算好,虽然算不上“吃嘛嘛香”,但是也算是不错了,但愿能增上几斤才好。
    怎么样,我们在Chapter Two最后提出的两个问题估计早出来了吧,:)
    First:为什么在HttpModule中不能使用Session?
    Second:系统默认的几个HttpModule在哪里配置的?

    我们先挑软柿子捏,第二个问题的答案是:在文件machine.config中配置,比如在你的系统文件目录中的C:WI

NNTMicrosoft.NETFrameworkv1.0.3705CONFIGmachine.config。
    虽然是个软柿子,但是还是有些东东在里面的,那就是machine.config和我们常见的web.config有什么关

系呢?在ASP.NET Framework启动处理一个Http Request的时候,她会依次加载machine.config以及你请求页面

所在目录的web.config文件,里面的配置是有<remove>标签的,什么意思不说也知道了吧。如果你在machine.c

onfig中配置了一个自己的HttpModule,你仍然可以在离你最近web.config文件中“remove”掉这个映射关系。
    至于第一个问题,呵呵,如果你仔细的运行过上次的文件但是没有自己仔细深入研究一下的话,一定会觉

得在HttpModule中的确无法使用Session,:)。如果你发现上次提出的问题本身就是一个"Problem",那么恭喜你,你没有掉进我故意给出的框框中,并且很有质疑精神,:)
    今天我们就来解释一下HttpModule和HttpHandler之间的关系,在今天的“日记”完成的时候,你也就会发现第一个问题的答案了。

    Chapter Three -- 深入HttpModule

    我们曾经提及当一个Http Request被ASP.NET Framework捕获之后会依次交给HttpModule以及HttpHandler来处理,但是不能理解为HttpModule和HttpHandler是完全独立的,实际上是,在Http Request在HttpModule传递的过程中会在某个事件内将控制权交给HttpHandler的,而真正的处理在HttpHandler中完成之后,再将控制权交还给HttpModule。也就是说HttpModule在某个请求经过她的时候会再恰当时候同HttpHandler进行通信,在何时,如何通信呢?这就是下面提到的了。
    我们提到在HttpModule中最开始的事件是BeginRequest,最终的事件是EndRequest。你如果仔细看上次给出的源程序的话,应当发现在方法Init()中参数我们传递的是一个HttpApplication类型,而我们曾经提及的两个事件正是这个传递进来的HttpApplication的事件之一。
    HttpApplication还有其它的事件,分别如下:
            application.BeginRequest
            application.EndRequest
            application.PreRequestHandlerExecute
            application.PostRequestHandlerExecute
            application.ReleaseRequestState
            application.AcquireRequestState
            application.AuthenticateRequest
            application.AuthorizeRequest
            application.ResolveRequestCache
            application.PreSendRequestHeaders
            application.PreSendRequestContent

      需要注意的是,在事件EndRequest之后还会继续执行application.PreSendRequestHeaders以及application.PreSendRequestContent事件,而这两个事件大家想必应当从名称上面看得出来事做什么用途的了吧。是的,一旦触发了这两个事件,就表明整个Http Request的处理已经完成了,在这两个事件中是开始向客户端传送处理完成的数据流了。看到这里,您应当有一个疑问才对:怎么没见到HttpHandler就处理完成了?不是提到过HttpHandler才是真正处理Http Request的吗?如果你有这个疑问表明你是仔细在看,也不枉我打字打得这莫累,:)。
    其实一开始我就提到了,在一个http request在HttpModule传递过程中,会在某一个时刻(确切的说应当是事件)中将这个请求传递给HttpHandler的。这个事件就是ResolveRequestCache,在这个事件之后,HttpModule会建立一个HttpHandler的入口实例(做好准备了,:)),但是此时并没有将控制权交出,而是继续触发AcquireRequestState以及PreRequestHandlerExecute事件(如果你实现了的话)。看到了吗,最后一个事件的前缀是Pre,呵呵。这表明下一步就要进入HttpHandler了,的确如此,正如我们猜想的那样,在PreRequestHandlerExecute事件之后,HttpModule就会将控制权暂时交给HttpHandler,以便进行真正的http request处理工作。而在HttpHandler内部会执行ProcessRequest来处理请求。在HttpHandler处理完毕之后,会将控制权交还给HttpModule,HttpModule便会继续对处理完毕的http Request进行层层的转交动作,直到返回到客户端。
   怎么样,是不是有些混乱?呵呵,苦于纯文本无法画流程图,我手头上已经画好了一个整个HttpModule的生命周期图,我只能暂且在这里用字符描绘一下前后流程了,如果你想要这个图片,可以给我发送mail,我给你mail过去。
   Http Request在整个HttpModule中的生命周期图:

                    Http Request开始
                        |
                       HttpModule
                            |
                     HttpModule.BeginRequest()
                        |
                HttpModule.AuthenticateRequest()
                        |
                      HttpModule.AuthorizeRequest()
                        |
                  HttpModule.ResolveRequestCache()
                        |
                    建立HttpHandler控制点
                        |
                接着处理(HttpHandler已经建立,此后Session可用)
                        |
                  HttpModule.AcquireRequestState()
                        |
                HttpModule.PreRequestHandlerExecute()
                        |
                   进入HttpHandler处理HttpRequest
                        |
                    HttpHandler.ProcessRequest()
                        |
            返回到HttpModule接着处理(HttpHandler生命周期结束,Session失效)
                        |
                HttpModule.PostRequestHandlerExecute()
                        |
                HttpModule.ReleaseRequestState()
                        |
                HttpModule.UpdateRequestCache()
                        |
                    HttpModule.EndRequest()
                        |
                HttpModule.PreSendRequestHeaders()
                        |
                HttpModule.PreSendRequestContent()
                        |
                    将处理后的数据返回客户端
                        |
                         整个Http Request处理结束
   
   
     怎么样,从上面的图中应当找到上次我们提出的第一个问题的答案了吧。
     为了验证上面的流程,我们可以用下面的这个自己的HttpModuel来验证一下就知道了。
     注意我们下面给出的是类的内容,框架还是前次我们给出的那个,自己加上就好了:

        public void Init(HttpApplication application)
        {
            application.BeginRequest += (new EventHandler(this.Application_BeginRequest));
            application.EndRequest += (new EventHandler(this.Application_EndRequest));
            application.PreRequestHandlerExecute +=(new EventHandler(this.Application_PreRequestHandlerExecute));
            application.PostRequestHandlerExecute  +=(new EventHandler(this.Application_PostRequestHandlerExecute));
            application.ReleaseRequestState  +=(new EventHandler(this.Application_ReleaseRequestState));
            application.AcquireRequestState +=(new EventHandler(this.Application_AcquireRequestState));
            application.AuthenticateRequest +=(new EventHandler(this.Application_AuthenticateRequest));
            application.AuthorizeRequest +=(new EventHandler(this.Application_AuthorizeRequest));
            application.ResolveRequestCache +=(new EventHandler(this.Application_ResolveRequestCache));
            application.PreSendRequestHeaders +=(new EventHandler(this.Application_PreSendRequestHeaders));
            application.PreSendRequestContent +=(new EventHandler(this.Application_PreSendRequestContent));
        }
   
        private void Application_PreRequestHandlerExecute(Object source, EventArgs e)
        {
            HttpApplication application = (HttpApplication)source;
            HttpContext context = application.Context;

            context.Response.Write("Application_PreRequestHandlerExecute<br>");
        }

        private void Application_BeginRequest(Object source, EventArgs e)
        {
            HttpApplication application = (HttpApplication)source;
            HttpContext context = application.Context;

            context.Response.Write("Application_BeginRequest<br>");
        }
   
        private void Application_EndRequest(Object source, EventArgs e)
        {
            HttpApplication application = (HttpApplication)source;
            HttpContext context = application.Context;

            context.Response.Write("Application_EndRequest<br>");

        }        
   
        private void Application_PostRequestHandlerExecute(Object source,EventArgs e)
        {
            HttpApplication application = (HttpApplication)source;
            HttpContext context = application.Context;

            context.Response.Write("Application_PostRequestHandlerExecute<br>");

        }

        private void Application_ReleaseRequestState(Object source, EventArgs e)
        {
            HttpApplication application = (HttpApplication)source;
            HttpContext context = application.Context;

            context.Response.Write("Application_ReleaseRequestState<br>");

        }

        private void Application_UpdateRequestCache(Object source, EventArgs e)
        {
            HttpApplication application = (HttpApplication)source;
            HttpContext context = application.Context;

            context.Response.Write("Application_UpdateRequestCache<br>");

        }

        private void Application_AuthenticateRequest(Object source, EventArgs e)
        {
            HttpApplication application = (HttpApplication)source;
            HttpContext context = application.Context;

            context.Response.Write("Application_AuthenticateRequest<br>");

        }

        private void Application_AuthorizeRequest(Object source, EventArgs e)
        {
            HttpApplication application = (HttpApplication)source;
            HttpContext context = application.Context;

            context.Response.Write("Application_AuthorizeRequest<br>");

        }

        private void Application_ResolveRequestCache(Object source, EventArgs e)
        {
            HttpApplication application = (HttpApplication)source;
            HttpContext context = application.Context;

            context.Response.Write("Application_ResolveRequestCache<br>");

        }

        private void Application_AcquireRequestState(Object source, EventArgs e)
        {
            HttpApplication application = (HttpApplication)source;
            HttpContext context = application.Context;

            context.Response.Write("Application_AcquireRequestState<br>");

        }

        private void Application_PreSendRequestHeaders(Object source, EventArgs e)
        {
            HttpApplication application = (HttpApplication)source;
            HttpContext context = application.Context;

            context.Response.Write("Application_PreSendRequestHeaders<br>");

        }

        private void Application_PreSendRequestContent(Object source, EventArgs e)
        {
            HttpApplication application = (HttpApplication)source;
            HttpContext context = application.Context;

            context.Response.Write("Application_PreSendRequestContent<br>");

        }
        public void Dispose()
        {
        }


       好了,手累的不行了,:)
       老规矩,下面的问题仔细考虑:
       HttpModule中的Application的多个事件和Global.asax中的Application事件有联系吗?如果有,该会有哪些联系呢?

       下回会探讨HttpHandler的构建了,:)
       不过最近挺忙,不知道何时能继续......尽力吧。
       See you later.

posted @ 2004-08-30 17:21 停留.Xp 阅读(917) 评论(5) 编辑

ASP.NET Framework深度历险(2)

ASP.NET Framework深度历险(2)


March 25,2004
.NET Framework Version:1.0.3705正式版
VS.NET(C#) Version:7.0.9466正式版


    刚刚吃完晚饭,正好在键盘上面锻炼一下手指。
    接着上回继续写这个“日记”:
   
    Chapter Two -- HttpModule是如何工作的?

    我们上回说到,一个来自于客户端的Http Request被截获后经过层层转交(怎么都在踢皮球?呵呵)到达了HttpModule这个“请求监听器”。
HttpModule就类似于安插在ASPNET_WP.EXE进程中的一个***,稍微有些常识的人都会很自然的想象得到***是用来做什么的,而我们的HttpModule
可以说是作***的绝好人选了,但是需要明确的是,HttpModule绝对不是简单的监听器,它可以做到更多的东西,比如它可以对截获的请求增加一些内容
等等。
     另外需要明白的是,当一个Http Request到达HttpModule的时候,整个ASP.NET Framework系统还并没有对这个请求做任何的真正处理,但是我们可以
在这个Http Request传递到真正的请求处理中心(HttpHandler)之前附加一些我们需要的信息在这个Http Request至上,或者针对我们截获的这个Http
Request信息作一些额外的工作,或者在某些情况下干脆终止满足一些条件的Http Request,从而可以起到一个Filter过滤器的作用,而不仅仅是一个***了。
     通过查阅MSDN(不要去相信.NET SDK自带的那个QuickStarts Web文档,正式版本中竟然在很多地方没有更新这个文档,很多东西在正式版本是无效的),
你会发现系统HttpModule实现了一个叫做IHttpModule的接口,很自然的就应当想到,只要我们自己的类能够实现IHttpModule接口,不就可以完全替代系统的
HttpModule了吗?完全正确。
     在我们开始自己的HttpModule类之前,我先来告诉你系统中的那个HttpModule是什么样子的,ASP.NET系统中默认的HttpModule有以下几个:
            System.Web.Caching.OutputCacheModule
            System.Web.SessionState.SessionStateModule
            System.Web.Security.WindowsAuthenticationModule
            System.Web.Security.FormsAuthenticationModule
            System.Web.Security.PassportAuthenticationModule
            System.Web.Security.UrlAuthorizationModule
            System.Web.Security.FileAuthorizationModule
     
     好了,我们来开始我们自己的HttpModule构建历程吧。
     1)打开VS.NET新建一个“Class Library”项目,将它命名为MyHttpModule。
     2)引用System.Web.dll文件

     在代码区域敲入:

using System;
using System.Web;

namespace MyHttpModuleTest
{
    /// <summary>
    /// 说明:用来实现自定义HttpModule的类
    /// 作者:uestc95
    /// 联系:uestc95@263.net
    /// </summary>
    public class MyHttpModule:IHttpModule
    {
        /// <summary>
        /// 说明:构造器方法
        /// 作者:uestc95
        /// 联系:uestc95@263.net
        /// </summary>
        public MyHttpModule()
        {
            
        }

        /// <summary>
        /// 说明:实现IHttpModule接口的Init方法
        /// 作者:uestc95
        /// 联系:uestc95@263.net
        /// </summary>
        /// <param name="application">HttpApplication类型的参数</param>
        public void Init(HttpApplication application)
        {
            application.BeginRequest +=new EventHandler(this.Application_BeginRequest);
            application.EndRequest +=new EventHandler(this.Application_EndRequest);
        }

        /// <summary>
        /// 说明:自己定义的用来做点事情的私有方法
        /// 作者:uestc95
        /// 联系:uestc95@263.net
        /// </summary>
        /// <param name="obj">传递进来的对象参数</param>
        /// <param name="e">事件参数</param>
        private void Application_BeginRequest(Object obj,EventArgs e)
        {
            HttpApplication application=(HttpApplication)obj;
            HttpContext context=application.Context;
            HttpResponse response=context.Response;
            HttpRequest request=context.Request;

            response.Write("我来自Application_BeginRequest,:)");
            
        }

        /// <summary>
        /// 说明:自己定义的用来做点事情的私有方法
        /// 作者:uestc95
        /// 联系:uestc95@263.net
        /// </summary>
        /// <param name="obj">传递进来的对象参数</param>
        /// <param name="e">事件参数</param>
        private void Application_EndRequest(Object obj,EventArgs e)
        {
            HttpApplication application=(HttpApplication)obj;
            HttpContext context=application.Context;
            HttpResponse response=context.Response;
            HttpRequest request=context.Request;

            response.Write("我来自Application_EndRequest,:)");
            
        }

        /// <summary>
        /// 说明:实现IHttpModule接口的Dispose方法
        /// 作者:uestc95
        /// 联系:uestc95@263.net
        /// </summary>
        public void Dispose(){}
    }
}

     3)在VS.NET中编译之后,你会得到MyHttpModule.dll这个文件。
     4)接下来我们的工作就是如何让ASPNET_WP.exe进程将http request交给我们自己写的这个HttpModule呢?方法就是配置web.config文件。
     在web.config文件中增加如下几句话:
        <httpModules>
            <add name="test" type="MyHttpModuleTest.MyHttpModule,MyHttpModule"/>
        </httpModules>
      注意要区分大小写,因为web.config作为一个XML文件是大小写敏感的。“type=MyHttpModuleTest.MyHttpModule,MyHttpModule”告诉我们
      系统将会将http request请求交给位于MyHttpModule.dll文件中的MyHttpModuleTest.MyHttpModule类去处理。而这个DLL文件系统将会自动
      到bin子目录或者系统全局程序集缓冲区(GAC)搜寻。我们可以将我们刚才得到的DLL文件放在bin子目录中,至于后者,你可以通过.NET SDK正式版
      自带的Config工具做到,我们不详细说了。


      好了,我们的用来截获http request请求的HttpModule就完成并且装配完成了,你可以试着在你的web项目中建立一个新的WebForm,运行看看呢?:)
      最后,我们假设一个使用这个HttpModule的场合。A站点提供免费的ASP.NET虚拟空间给大家,但是A站点的管理者并不想提供免费的午餐,他想要在每一个
      页面被浏览的时候自动弹出自己公司的广告(就像现在的www.X63.com一样),我总不能时刻监视所有用户的所有页面吧,并且想要在每一个页面手动添加
      一段JS代码,工作量是不可想象的,也是不现实的。那末好了,只要我们的HttpModule一旦被挂接完成,这一切都将是轻而易举的事情了,只要我们在每一个
      Http Request被我们捕获的时候,给他增加上一些JS就好了!

      我们上面提到在Init()方法中使用了两个事件BeginRequest和EndRequest,这两个事件分别是Init()中可以处理的所有事件的最开始事件和最终事件,在他们
      中间还有一些其它的事件可以被我们利用,可以查阅MSDN。

      另外在我关闭EditPlus之前,需要敲下如下的话:
      在HttpModule中可以正常使用Response,Request,Server,Application,但是不能操作任何与Session有关代码!
      为什么呢?自己考虑一下吧,下回看看原因在哪里,另外再给出一个问题,你能发现系统默认的那几个HttpModule在哪里配置的呢?找找看。
      
      下回我们看看HttpHandler部分以及如何同HttpModule相配合的东东。

      See you later.

posted @ 2004-08-30 17:20 停留.Xp 阅读(746) 评论(2) 编辑

ASP.NET Framework深度历险(1)

ASP.NET Framework深度历险(1)


March 25,2004
记得前一段时间有本不错的书叫Delphi深度历险,写得不错,我也就暂且借用了,:)
在这里我不打算简单介绍ASP.NET的入门知识了,ASP.NET除了名字和古老的ASP有些相同外,已经是完完全全的改变了,虽然你仍能在ASP.NET中发现你熟悉的Session,Application等等,但是不要尝试将他们同远古的ASP时代的Session等等画上等号。
我们来慢慢的深入到ASP.NET Framework的核心内部,看看她是如何实现的,看看她是如何能承担起下一代Web开发技术平台这个美誉的。
这篇东东不曾想过要完成多少章节,也没有这个必要,权当日记的形式存在,或许很短,或许很长,我会尽我的所能来将ASP.NET Framework展现在诸位面前。
如果你对ASP.NET Framework没有任何了解,你同样可以成为ASP.NET coding高手,如果是这样,你就不必继续看下去了。

Chapter One --  Process a http request.

我们瞧一瞧ASP.NET Framework的运行机制和架构。
在开始之前,我们先跟随考古学家参观一下古老的ASP运行机制:
当你请求一个*.asp文件的时候,这个http request首先被inetinfo.exe进程所截获,这个inetinfo.exe进程就是WWW服务进程,然后她会将这个请求转交给asp.dll进程,asp.dll进程就会解释执行这个asp叶面,然后将解释后的数据流返回给客户端浏览器。

转过头来我们看看如今的ASP.NET Framework是如何处理一个http request.
当你请求一个*.aspx文件的时候,同样的这个http request会被inetinfo.exe进程截获,她判断文件的后缀之后,将这个请求转交给ASPNET_ISAPI.dll,ASPNET_ISAPI.dll会通过一个被称为Http PipeLine的管道,将请求发送给ASPNET_WP.exe进程,当这个http request进入ASPNET_WP.exe进程之后,会通过HttpRuntime来处理这个请求,处理完毕将结果返回客户端。

OK,好像并没有太大的改进嘛,不要着急,在ASP.NET Framework中我们甚至能够了解到HttpRuntime的细节。好,继续深入下去:
当Http Request进入HttpRuntime之后,会继续进入到一个被称之为HttpApplication Factory的一个Container中,她会给出一个HttpApplication来处理传递进来的请求,这个请求会依次进入如下几个Container:HttpModule->HttpHandler Factory->HttpHandler。
当系统内部的HttpHandler的ProcessResquest方法处理完毕之后,整个Http Request就完成了,客户端也就得到相应的东东了。

整理一下ASP.NET Framework处理一个Http Request的流程:

HttpRequest-->inetinfo.exe-->ASPNET_ISAPI.dll-->Http Pipeline-->ASPNET_WP.exe-->HttpRuntime-->HttpApplication Factory-->HttpApplication-->HttpModule-->HttpHandler Factory-->HttpHandler-->HttpHandler.ProcessRequest()

或许会问,我知道这个处理流程有什么用处呢?当然有用了,比如如果你想要中途截获一个Http Request并且做些自己的处理,该如何做呢?这是下一次我们探讨的东东了,下次我们详细讨论处理的细节问题。
see you later

posted @ 2004-08-30 17:20 停留.Xp 阅读(912) 评论(2) 编辑

2004年8月26日

Create a Custom ProgressBar Control

The ProgressBar control that is included with Visual C# .NET supports only the standard setting.

The sample code in this article illustrates how to create a control that supports the following properties:
  • Minimum. This property obtains or sets the lower value for the range of valid values for progress. The default value of this property is zero (0); you cannot set this property to a negative value.
  • Maximum. This property obtains or sets the upper value for the range of valid values for progress. The default value of this property is 100.
  • Value. This property obtains or sets the current level of progress. The value must be in the range that the Minimum and the Maximum properties define.
  • ProgressBarColor. This property obtains or sets the color of the progress bar.
back to the top

Create a Custom ProgressBar Control

  1. Follow these steps to create a new Windows Control Library project in Visual C# .NET:
    1. Start Microsoft Visual Studio .NET.
    2. On the File menu, point to New, and then click Project.
    3. In the New Project dialog box, click Visual C# Projects under Project Types, and then click Windows Control Library under Templates.
    4. In the Name box, type SmoothProgressBar, and then click OK.
    5. In Project Explorer, rename the default class module from UserControl1.cs to SmoothProgressBar.cs.
    6. In the Properties window for the UserControl object, change the Name property from UserControl1 to SmoothProgressBar.
  2. At this point, you typically inherit from the class of that control and then add the additional functionality to extend an existing control. However, the ProgressBar class is sealed and cannot be inherited. Therefore, you must build the control from the beginning.

    Add the following code to the class module of the UserControl, just after the "Windows Form Designer generated code" section:
    int min = 0;	// Minimum value for progress range
    int max = 100;	// Maximum value for progress range
    int val = 0;		// Current progress
    Color BarColor = Color.Blue;		// Color of progress meter
    
    protected override void OnResize(EventArgs e)
    {
    	// Invalidate the control to get a repaint.
    	this.Invalidate();
    }
    
    protected override void OnPaint(PaintEventArgs e)
    {
    	Graphics g = e.Graphics;
    	SolidBrush brush = new SolidBrush(BarColor);
    	float percent = (float)(val - min) / (float)(max - min);
    	Rectangle rect = this.ClientRectangle;
    
    	// Calculate area for drawing the progress.
    	rect.Width = (int)((float)rect.Width * percent);
    
    	// Draw the progress meter.
    	g.FillRectangle(brush, rect);
    
    	// Draw a three-dimensional border around the control.
    	Draw3DBorder(g);
    
    	// Clean up.
    	brush.Dispose();
    	g.Dispose();		
    }
    
    public int Minimum
    {
    	get
    	{
    		return min;
    	}
    
    	set
    	{
    		// Prevent a negative value.
    		if (value < 0)
    		{
    			min = 0;
    		}
    		
    		// Make sure that the minimum value is never set higher than the maximum value.
    		if (value > max)
    		{
    			min = value;
    			min = value;
    		}
    		
    		// Ensure value is still in range
    		if (val < min)
    		{
    			val = min;
    		}
    
    		// Invalidate the control to get a repaint.
    		this.Invalidate();
    	}
    }
    
    public int Maximum
    {
    	get
    	{
    		return max;
    	}
    
    	set
    	{
    		// Make sure that the maximum value is never set lower than the minimum value.
    		if (value < min)
    		{
    			min = value;
    		}
    
    		max = value;
    
    		// Make sure that value is still in range.
    		if (val > max)
    		{
    			val = max;
    		}
    
    		// Invalidate the control to get a repaint.
    		this.Invalidate();
    	}
    }
    
    public int Value
    {
    	get
    	{
    		return val;
    	}
    
    	set
    	{
    		int oldValue = val;
    
    		// Make sure that the value does not stray outside the valid range.
    		if (value < min)
    		{
    			val = min;
    		}
    		else if (value > max)
    		{
    			val = max;
    		}
    		else
    		{
    			val = value;
    		}
    
    		// Invalidate only the changed area.
    		float percent;
    
    		Rectangle newValueRect = this.ClientRectangle;
    		Rectangle oldValueRect = this.ClientRectangle;
    
    		// Use a new value to calculate the rectangle for progress.
    		percent = (float)(val - min) / (float)(max - min);
    		newValueRect.Width = (int)((float)newValueRect.Width * percent);
    
    		// Use an old value to calculate the rectangle for progress.
    		percent = (float)(oldValue - min) / (float)(max - min);
    		oldValueRect.Width = (int)((float)oldValueRect.Width * percent);
    
    		Rectangle updateRect = new Rectangle();
    		
    		// Find only the part of the screen that must be updated.
    		if (newValueRect.Width > oldValueRect.Width)
    		{
    			updateRect.X = oldValueRect.Size.Width;
    			updateRect.Width = newValueRect.Width - oldValueRect.Width;
    		}
    		else
    		{
    			updateRect.X = newValueRect.Size.Width;
    			updateRect.Width = oldValueRect.Width - newValueRect.Width;
    		}
    
    		updateRect.Height = this.Height;
    
    		// Invalidate the intersection region only.
    		this.Invalidate(updateRect);
    	}
    }
    
    public Color ProgressBarColor
    {
    	get
    	{
    		return BarColor;
    	}
    
    	set
    	{
    		BarColor = value;
    
    		// Invalidate the control to get a repaint.
    		this.Invalidate();
    	}
    }
    
    private void Draw3DBorder(Graphics g)
    {
    	int PenWidth = (int)Pens.White.Width;
    
    	g.DrawLine(Pens.DarkGray, 
    		new Point(this.ClientRectangle.Left, this.ClientRectangle.Top),
    		new Point(this.ClientRectangle.Width - PenWidth, this.ClientRectangle.Top));
    	g.DrawLine(Pens.DarkGray,
    		new Point(this.ClientRectangle.Left, this.ClientRectangle.Top), 
    		new Point(this.ClientRectangle.Left, this.ClientRectangle.Height - PenWidth));
    	g.DrawLine(Pens.White,
    		new Point(this.ClientRectangle.Left, this.ClientRectangle.Height - PenWidth), 
    		new Point(this.ClientRectangle.Width - PenWidth, this.ClientRectangle.Height - PenWidth));
    	g.DrawLine(Pens.White,
    		new Point(this.ClientRectangle.Width - PenWidth, this.ClientRectangle.Top), 
    		new Point(this.ClientRectangle.Width - PenWidth, this.ClientRectangle.Height - PenWidth));
    } 
    					
  3. On the Build menu, click Build Solution to compile the project.
back to the top

Create a Sample Client Application

  1. On the File menu, point to New, and then click Project.
  2. In the Add New Project dialog box, click Visual C# Projects under Project Types, click Windows Application under Templates, and then click OK.
  3. Follow these steps to add two instances of the SmoothProgressBar control to the form:
    1. On the Tools menu, click Customize Toolbox.
    2. Click the .NET Framework Components tab.
    3. Click Browse, and then locate the SmoothProgressBar.dll file, which you created in the "Create a Custom ProgressBar Control" section.
    4. Click OK. Notice that the SmoothProgressBar control is added to the toolbox.
    5. Drag two instances of the SmoothProgressBar control from the toolbox to the default form of the Windows Application project.
  4. Drag a Timer control from the toolbox to the form.
  5. Add the following code to the Tick event of the Timer control:
    if (this.smoothProgressBar1.Value > 0)
    {
    	this.smoothProgressBar1.Value--;
    	this.smoothProgressBar2.Value++;
    }
    else
    {
    	this.timer1.Enabled = false;
    } 
    					
  6. Drag a Button control from the toolbox to the form.
  7. Add the following code to the Click event of the Button control:
    this.smoothProgressBar1.Value = 100;
    this.smoothProgressBar2.Value = 0;
    			
    this.timer1.Interval = 1;
    this.timer1.Enabled = true; 
    					
  8. On the Debug menu, click Start to run the sample project.
  9. Click the button. Notice that the two progress indicators display the text "progress". One progress indicator displays the progress in an increasing manner, and the other progress indicator displays the progress in a decreasing or a countdown manner.
back to the top

posted @ 2004-08-26 16:15 停留.Xp 阅读(839) 评论(0) 编辑

于多线程的超时终止代码

using System;
using System.Threading;

public delegate void EventOverHandler(int x);

public class ThreadTimeOut
{
    
protected Thread threadObj;
    
protected int timeOut;

    
public Thread ThreadObj
    
{
        
set { threadObj = value; }
        
get return threadObj; }
    }


    
public int TimeOut
    
{
        
set { timeOut = value; }
        
get return timeOut; }
    }


    
public virtual void OnTimeOut()
    
{
        
if(ThreadObj != null)
        
{
            
if(!ThreadObj.Join(timeOut))
            
{
                ThreadObj.Abort();
            }

        }

    }

}


public class ThreadTest : ThreadTimeOut
{
    
private int x;

    
public ThreadTest(int x)
    
{
        
this.x = x;
    }


    
public EventOverHandler onOver;

    
public void ThreadRun()
    
{
        ThreadObj 
= new Thread(new ThreadStart(ThreadFunction));
        Thread th 
= new Thread(new ThreadStart(OnTimeOut));
        ThreadObj.Start();
        th.Start();
    }


    
public void ThreadFunction()
    
{
        
for (int i = 0; i < 10; i++)
        
{
            Console.WriteLine(x);
            Thread.Sleep(
500);
            
++x;
        }

        onOver(x);
    }


    
public override void OnTimeOut()
    
{
        
if(ThreadObj != null)
        
{
            
if(!ThreadObj.Join(timeOut))
            
{
                ThreadObj.Abort();
                onOver(x);
            }

        }

    }

}


class test
{
    
public static void Main()
    
{
        
for(int i = 0; i < 10; i++)
        
{
            ThreadTest tt 
= new ThreadTest(i * 10);
            tt.onOver 
= new EventOverHandler(test.showMsg);
            tt.TimeOut 
= 1000;
            tt.ThreadRun();
        }

    }


    
public static void showMsg(int xx)
    
{
        Console.WriteLine(
"The End {0}.", xx);
    }

}

posted @ 2004-08-26 11:45 停留.Xp 阅读(937) 评论(0) 编辑

Safe, Simple Multithreading in Windows Forms, Part 1

Safe, Simple Multithreading in Windows Forms, Part 1

Chris Sells

June 28, 2002

Download the AsynchCalcPi.exe sample.

It all started innocently enough. I found myself needing to calculate the area of a circle for the first time in .NET. This called, of course, for an accurate representation of pi. System.Math.PI is handy, but since it only provides 20 digits of precision, I was worried about the accuracy of my calculation (I really needed 21 digits to be absolutely comfortable). So, like any programmer worth their salt, I forgot about the problem I was actually trying to solve and I wrote myself a program to calculate pi to any number of digits that I felt like. What I came up with is shown in Figure 1.

Figure 1. Digits of Pi application

Progress on Long-Running Operations

While most applications don't need to calculate digits of pi, many kinds of applications need to perform long-running operations, whether it's printing, making a Web service call, or calculating interest earnings on a certain billionaire in the Pacific Northwest. Users are generally content to wait for such things, often moving to something else in the meantime, so long as they can see that progress is being made. That's why even my little application has a progress bar. The algorithm I'm using calculates pi nine digits at a time. As each new set of digits are available, my program keeps the text updated and moves the progress bar to show how we're coming along. For example, Figure 2 shows progress on the way to calculating 1000 digits of pi (if 21 digits are good, than 1000 must be better).

Figure 2. Calculating pi to 1000 digits

The following shows how the user interface (UI) is updated as the digits of pi are calculated:

void ShowProgress(string pi, int totalDigits, int digitsSoFar) {
  _pi.Text = pi;
  _piProgress.Maximum = totalDigits;
  _piProgress.Value = digitsSoFar;
}

void CalcPi(int digits) {
  StringBuilder pi = new StringBuilder("3", digits + 2);

  // Show progress
  ShowProgress(pi.ToString(), digits, 0);

  if( digits > 0 ) {
    pi.Append(".");

    for( int i = 0; i < digits; i += 9 ) {
      int nineDigits = NineDigitsOfPi.StartingAt(i+1);
      int digitCount = Math.Min(digits - i, 9);
      string ds = string.Format("{0:D9}", nineDigits);
      pi.Append(ds.Substring(0, digitCount));

      // Show progress
      ShowProgress(pi.ToString(), digits, i + digitCount);
    }
  }
}

Everything was going along fine until, in the middle of actually calculating pi to 1000 digits, I switched away to do something else and then switched back. What I saw is shown in Figure 3.

Figure 3. No paint event for you!

The problem, of course, is that my application is single-threaded, so while the thread is calculating pi, it can't also be drawing the UI. I didn't run into this before because when I set the TextBox.Text and ProgressBar.Value properties, those controls would force their painting to happen immediately as part of setting the property (although I noticed that the progress bar was better at this than the text box). However, once I put the application into the background and then the foreground again, I need to paint the entire client area, and that's a Paint event for the form. Since no other event is going to be processed until we return from the event we're already processing (that is, the Click event on the Calc button), we're out of luck in terms of seeing any further progress. What I really needed to do was free the UI thread for doing UI work and handle the long-running process in the background. For this, I need another thread.

Asynchronous Operations

My current synchronous Click handler looked like this:

void _calcButton_Click(object sender, EventArgs e) {
  CalcPi((int)_digits.Value);
}

Recall that the issue is until CalcPi returns, the thread can't return from our Click handler, which means the form can't handle the Paint event (or any other event, for that matter). One way to handle this is to start another thread, like so:

using System.Threading;
…
int _digitsToCalc = 0;

void CalcPiThreadStart() {
  CalcPi(_digitsToCalc);
}

void _calcButton_Click(object sender, EventArgs e) {
  _digitsToCalc = (int)_digits.Value;
  Thread piThread = new Thread(new ThreadStart(CalcPiThreadStart));            
  piThread.Start();
}

Now, instead of waiting for CalcPi to finish before returning from the button Click event, I'm creating a new thread and asking it to start. The Thread.Start method will schedule my new thread as ready to start and then return immediately, allowing our UI thread to get back to its own work. Now, if the user wants to interact with the application (put it in the background, move it to the foreground, resize it, or even close it), the UI thread is free to handle all of those events while the worker thread calculates pi at its own pace. Figure 4 shows the two threads doing the work.

Figure 4. Naive multithreading

You may have noticed that I'm not passing any arguments to the worker thread's entry point—CalcPiThreadStart. Instead, I'm tucking the number of digits to calculate into a field, _digitsToCalc, calling the thread entry point, which is calling CalcPi in turn. This is kind of a pain, which is one of the reasons that I prefer delegates for asynchronous work. Delegates support taking arguments, which saves me the hassle of an extra temporary field and an extra function between the functions I want to call.

If you're not familiar with delegates, they're really just objects that call static or instance functions. In C#, they're declared using function declaration syntax. For example, a delegate to call CalcPi looks like this:

delegate void CalcPiDelegate(int digits);

Once I have a delegate, I can create an instance to call the CalcPi function synchronously like so:

void _calcButton_Click(object sender, EventArgs e) {
  CalcPiDelegate  calcPi = new CalcPiDelegate(CalcPi);
  calcPi((int)_digits.Value);
}

Of course, I don't want to call CalcPi synchronously; I want to call it asynchronously. Before I do that, however, we need to understand a bit more about how delegates work. My delegate declaration above declares a new class derived from MultiCastDelegate with three functions, Invoke, BeginInvoke, and EndInvoke, as shown here:

class CalcPiDelegate : MulticastDelegate {
  public void Invoke(int digits);
  public void BeginInvoke(int digits, AsyncCallback callback,
                          object asyncState);
  public void EndInvoke(IAsyncResult result);
}

When I created an instance of the CalcPiDelegate earlier and then called it like a function, I was actually calling the synchronous Invoke function, which in turn called my own CalcPi function. BeginInvoke and EndInvoke, however, are the pair of functions that allow you to invoke and harvest the results of a function call asynchronously. So, to have the CalcPi function called on another thread, I need to call BeginInvoke like so:

void _calcButton_Click(object sender, EventArgs e) {
  CalcPiDelegate  calcPi = new CalcPiDelegate(CalcPi);
  calcPi.BeginInvoke((int)_digits.Value, null, null);
}

Notice that we're passing nulls for the last two arguments of BeginInvoke. These are needed if we'd like to harvest the result from the function we're calling at some later date (which is also what EndInvoke is for). Since the CalcPi function updates the UI directly, we don't need anything but nulls for these two arguments. If you'd like the details of delegates, both synchronous and asynchronous, see .NET Delegates: A C# Bedtime Story.

At this point, I should be happy. I've got my application to combine a fully interactive UI that shows progress on a long-running operation. In fact, it wasn't until I realized what I was really doing that I became unhappy.

Multithreaded Safety

As it turned out, I had just gotten lucky (or unlucky, depending on how you characterize such things). Microsoft Windows® XP was providing me with a very robust implementation of the underlying windowing system on which Windows Forms is built. So robust, in fact, that it gracefully handled my violation of the prime directive of Windows programming—Though shalt not operate on a window from other than its creating thread. Unfortunately there's no guarantee that other, less robust implementations of Windows would be equally graceful given my bad manners.

The problem, of course, was of my own making. If you remember Figure 4, I had two threads accessing the same underlying window at the same time. However, because long-running operations are so common in Windows application, each UI class in Windows Forms (that is, every class that ultimately derives from System.Windows.Forms.Control) has a property that you can use from any thread so that you can access the window safely. The name of the property is InvokeRequired, which returns true if the calling thread needs to pass control over to the creating thread before calling a method on that object. A simple Assert in my ShowProgress function would have immediately shown me the error of my ways:

using System.Diagnostics;

void ShowProgress(string pi, int totalDigits, int digitsSoFar) {
  // Make sure we're on the right thread
  Debug.Assert(_pi.InvokeRequired == false);
  ...
}

In fact, the .NET documentation is quite clear on this point. It states, "There are four methods on a control that are safe to call from any thread: Invoke, BeginInvoke, EndInvoke, and CreateGraphics. For all other method calls, you should use one of the invoke methods to marshal the call to the control's thread." So, when I set the control properties, I'm clearly violating this rule. And from the names of the first three functions that I'm allowed to call safely (Invoke, BeginInvoke, and EndInvoke), it should be clear that I need to construct another delegate that will be executed in the UI thread. If I were worried about blocking my worker thread, like I was worried about blocking my UI thread, I'd need to use the asynchronous BeginInvoke and EndInvoke. However, since my worker thread exists only to service my UI thread, let's use the simpler, synchronous Invoke method, which is defined like this:

public object Invoke(Delegate method);
public object Invoke(Delegate method, object[] args);

The first overload of Invoke takes an instance of a delegate containing the method we'd like to call in the UI thread, but assumes no arguments. However, the function we want to call to update the UI, ShowProgress, takes three arguments, so we'll need the second overload. We'll also need another delegate for our ShowProgress method so that we can pass the arguments correctly. Here's how to use Invoke to make sure that our calls to ShowProgress, and therefore our use of our windows, shows up on the correct thread (making sure to replace both calls to ShowProgress in CalcPi):

delegate
void ShowProgressDelegate(string pi, int totalDigits, int digitsSoFar);

void CalcPi(int digits) {
  StringBuilder pi = new StringBuilder("3", digits + 2);

  // Get ready to show progress asynchronously
  ShowProgressDelegate showProgress =
    new ShowProgressDelegate(ShowProgress);

  // Show progress
  this.Invoke(showProgress, new object[] { pi.ToString(), digits, 0});

  if( digits > 0 ) {
    pi.Append(".");

    for( int i = 0; i < digits; i += 9 ) {
      ...
      // Show progress
      this.Invoke(showProgress,
        new object[] { pi.ToString(), digits, i + digitCount});
    }
  }
}

The use of Invoke has finally given me a safe use of multithreading in my Windows Forms application. The UI thread spawns a worker thread to do the long-running operation, and the worker thread passes control back to the UI thread when the UI needs updating. Figure 5 shows our safe multithreading architecture.

Figure 5. Safe multithreading

Simplified Multithreading

The call to Invoke is a bit cumbersome, and because it happens twice in our CalcPi function, we could simplify things and update ShowProgress itself to do the asynchronous call. If ShowProgress is called from the correct thread, it will update the controls, but if it's called from the incorrect thread, it uses Invoke to call itself back on the correct thread. This lets us go back to the previous, simpler CalcPi:

void ShowProgress(string pi, int totalDigits, int digitsSoFar) {
  // Make sure we're on the right thread
  if( _pi.InvokeRequired == false ) {
    _pi.Text = pi;
    _piProgress.Maximum = totalDigits;
    _piProgress.Value = digitsSoFar;
  }
  else {
    // Show progress asynchronously
    ShowProgressDelegate showProgress =
      new ShowProgressDelegate(ShowProgress);
    this.Invoke(showProgress,
      new object[] { pi, totalDigits, digitsSoFar});
  }
}

void CalcPi(int digits) {
  StringBuilder pi = new StringBuilder("3", digits + 2);

  // Show progress
  ShowProgress(pi.ToString(), digits, 0);

  if( digits > 0 ) {
    pi.Append(".");

    for( int i = 0; i < digits; i += 9 ) {
      ...
      // Show progress
      ShowProgress(pi.ToString(), digits, i + digitCount);
    }
  }
}

Because Invoke is a synchronous call and we're not consuming the return value (in fact, ShowProgress doesn't have a return value), it's better to use BeginInvoke here so that the worker thread isn't held up, as shown here:

BeginInvoke(showProgress, new object[] { pi, totalDigits, digitsSoFar});

BeginInvoke is always preferred if you don't need the return of a function call because it sends the worker thread to its work immediately and avoids the possibility of deadlock.

Where Are We?

I've used this short example to demonstrate how to perform long-running operations while still showing progress and keeping the UI responsive to user interaction. To accomplish this, I used one asynch delegate to spawn a worker thread and the Invoke method on the main form, along with another delegate to be executed back in the UI thread.

One thing I was very careful never to do was to share access to a single point of data between the UI thread and the worker thread. Instead, I passed a copy of the data needed to do the work to the worker thread (the number of digits), and a copy of the data needed to update the UI (the digits calculated so far and the progress). In the final solution, I never passed references to objects that I was sharing between the two threads, such as a reference to the current StringBuilder (which would have saved me a string copy for every time I went back to the UI thread). If I had passed shared references back and forth, I would have had to use .NET synchronization primitives to make sure to that only one thread had access to any one object at a time, which would have been a lot of work. It was already enough work just to get the calls happening between the two threads without bringing synchronization into it.

Of course, if you've got large datasets that you're working with you're not going to want to copy data around. However, when possible, I recommend the combination of asynchronous delegates and message passing between the worker thread and the UI thread for implementing long-running tasks in your Windows Forms applications.

Acknowledgments

I'd like to thank Simon Robinson for his post on the DevelopMentor .NET mailing list that inspired this article, Ian Griffiths for his initial work in this area, Chris Andersen for his message-passing ideas, and last but certainly not least, Mike Woodring for the fabulous multithreading pictures that I lifted shamelessly for this article.

References


Chris Sells is an independent consultant, specializing in distributed applications in .NET and COM, as well as an instructor for DevelopMentor. He's written several books, including ATL Internals, which is in the process of being updated for ATL7. He's also working on Essential Windows Forms for Addison-Wesley and Mastering Visual Studio .NET for O'Reilly. In his free time, Chris hosts the Web Services DevCon and directs the Genghis source-available project. More information about Chris, and his various projects, is available at http://www.sellsbrothers.com.

posted @ 2004-08-26 11:02 停留.Xp 阅读(675) 评论(0) 编辑

socket编程

摘要: 下面的示例程序创建一个接收来自客户端的连接请求的服务器。该服务器是用异步套接字生成的,因此在等待来自客户端的连接时不挂起服务器应用程序的执行。该应用程序接收来自客户端的字符串,在控制台显示该字符串,然后将该字符串回显到客户端。来自客户端的字符串必须包含字符串“<EOF>”,以发出表示消息结尾的信号。[VisualBasic]ImportsSystemImport...阅读全文

posted @ 2004-08-26 10:48 停留.Xp 阅读(1036) 评论(0) 编辑

多线程学习

David Carmona
Premier Support for Developers
Microsoft Spain

June 2002

Summary: Provides an in-depth look at the thread pool support in the Microsoft .NET Framework, shows why you need a pool and the implementation provided in .NET, and includes a complete reference for its use in your applications. (25 printed pages)

Contents

Introduction
Thread Pool in .NET
Executing Functions on the Pool
Using Timers
Execution Based on Synchronization Objects
Asynchronous I/O Operations
Monitoring the Pool
Deadlocks
About Security
Conclusion
For More Information

Introduction

If you have experience with multithreaded programming in any programming language, you are already familiar with the typical examples of it. Usually, multithreaded programming is associated with user interface-based applications that need to perform a time-consuming operation without affecting the end user. Take any reference book and open it to the chapter dedicated to threads: can you find a multithreaded example that can perform a mathematical calculation running in parallel with your user interface?

It is not my intention that you throw away your books—don't do that! Multithreaded programming is just perfect for user interface-based applications. In fact, the Microsoft® .NET Framework makes multithreaded programming available to any language using Windows Forms, allowing the developer to design very rich interfaces with a better experience for the end user. However, multithreaded programming is not only for user interfaces; there are times that we need more than one execution stream without having any user interface in our application.

Let's use a hardware store client/server application as an example. The clients are the cash registers and the server is an application running on a separate machine in the warehouse. If you think about it, the server application can have no user interface at all. However, what would the implementation be without multithreading?

The server would receive requests from the clients via a channel (HTTP, sockets, files, etc.); it would process them and send a response back to the clients. Figure 1 shows how it would work.

Figure 1. Server application with one thread

The server application has to implement some kind of queue so that no requests are omitted. Figure 1 shows three requests arriving at the same time, but only one can be processed. While the server executes the request, "Decrease stock of monkey wrench," the other two must wait for their turns to be processed in the queue. When the execution of the first request is finished, the second one is next, and so on. This method is commonly used in many existing applications, but it poorly utilizes system resources. Imagine that decreasing the stock requires a modification of a file on disk. While this file is being written, the CPU will not be used even if in the meantime there are requests waiting to be processed. A general indication of these systems is a long response time with low CPU usage, even in stress conditions.

Another strategy used in current systems is to create different threads for each request. When a new request arrives, the server creates a new thread dedicated to the incoming request and it is destroyed when the execution finishes. The following diagram shows this case:

Figure 2. Multithreaded server application

As Figure 2 illustrates, we won't have a low CPU usage now—just the opposite. Even if it is not slow, creating and destroying threads is not optimum. If the operations performed by the thread are not complex, the extra time it takes to create and destroy threads can severely affect the final response time. Another point is the huge impact of these threads in stress conditions. Having all the requests executing at once on different threads would cause the CPU to reach 100% and most of the time would be wasted in context switching, even more than processing the request itself. Typical behaviors in this kind of system are an exponential increment of response time with the number of requests, and a high usage of CPU privileged time (this time can be viewed with the Task Manager and is affected by context switches between threads).

An optimum implementation is based on a hybrid of the previous two illustrations and introduces the concept of thread pool. When a request arrives, the application adds it to an incoming queue. A group of threads retrieves requests from this queue and processes them; as each thread is freed up, another request is executed from the queue. This schema is shown in the following figure:

Figure 3. Server application using a thread pool

In this example, we use a thread pool of two threads. When three requests arrive, they are immediately placed in the queue waiting to be processed; because both threads are free, the first two requests begin to execute. Once the process of any of these requests is finished, the free thread takes the third request and executes it. In this scenario, there is no need to create or destroy threads for each request; the threads are recycled between them. If the implementation of this thread pool is efficient, it will be able to add or remove threads from the pool for best performance. For example, when the pool is executing two requests and the CPU doesn't reach 50% utilization, it means that the executed requests are waiting for events or doing some kind of I/O operation. The pool can detect this situation and increase the number of threads so that more requests can be processed at the same time. In the opposite case when the CPU reaches 100% utilization, the pool decreases the number of threads to get more real CPU time without wasting it in context switches.

Thread Pool in .NET

Based on the above example, it is crucial to have an efficient implementation of the thread pool in enterprise applications. Microsoft realized this in the development of the .NET Framework; included in the heart of the system is an optimum thread pool ready for use.

This pool is not only available to applications that want to use it, but it is also integrated with most of the classes included in the Framework. In addition, there is important functionality of .NET built on the same pool. For example, .NET Remoting uses it to process requests on remote objects.

When a managed application is executed, the runtime offers a pool of threads that will be created the first time the code accesses it. This pool is associated with the physical process where the application is running, an important detail when you are using the functionality available in the .NET infrastructure to run several applications (called application domains) within the same process. If this is the case, one bad application can affect the rest within the same process because they all use the same pool.

You can use the thread pool or retrieve information about it through the class ThreadPool, in the System.Threading namespace. If you take a look at this class, you will see that all the members are static and there is no public constructor. This makes sense, because there's only one pool per process and we cannot create a new one. The purpose of this limitation is to centralize all the asynchronous programming in the same pool, so that we do not have a third-party component that creates a parallel pool that we cannot manage and whose threads are degrading our performance.

Executing Functions on the Pool

The ThreadPool.QueueUserWorkItem method allows us to launch the execution of a function on the system thread pool. Its declaration is as follows:

public static bool QueueUserWorkItem (WaitCallback callBack, object state)

The first parameter specifies the function that we want to execute on the pool. Its signature must match the delegate WaitCallback:

public delegate void WaitCallback (object state);

The state parameter allows any information to be passed to the method and it is specified in the call to QueueUserWorkItem. Let's see the implementation of our application for the hardware store with the new concepts:

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");
      }
   }
}

In this case, just to simplify the example, we have created a static method inside the main class that processes the requests. Because of the flexibility of delegates, we can specify any instance method to process requests, provided that it has the same signature as the delegate. In the example, the method implements a delay of two seconds with a call to Thread.Sleep, simulating the processing time.

If you compile and execute the last application you will see the following output:

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

Notice that all the requests have been processed on different threads in parallel. We can see it in more detail by adding the following code to both functions:

   // 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());

We have added a call to Thread.IsThreadPoolThread. This property returns True if the target thread belongs to the thread pool. In addition, we show the result of GetHashCode method from the current thread; this gives us a unique value with which to identify the executing thread. Take a look to the output now:

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

You can see how all of our requests execute on different threads belonging to the system pool thread. Launch the example again and notice the CPU utilization of your system. If you don't have any other application running in the background, it should be at almost 0%; because the only processing that is being done is suspending the execution for two seconds.

Let's modify the application. This time we don't suspend the thread that is processing the request; instead we will keep our system very busy. To do this, we can build a loop executing two seconds into each request using Environment.TickCount. This property returns the elapsed time in milliseconds since the last reboot of the system. Replace the "Thread.Sleep(2000)" line with the following code:

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

Now examine the CPU utilization from the Task Manager, and you will see that the application utilizes 100% of CPU time. Take a look at the output of our application:

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

Notice that the third request is not processed until the first one finishes, and it reuses thread number 7 for its execution. The reason is that the thread pool detects that the CPU is at 100% and it decides to wait until a thread is free, without creating a new one. This way there are less context switches and overall performance is better.

Using Timers

If you have developed Microsoft Win32® applications, you know the function SetTimer is part of its API. With this function you can specify a window that receives WM_TIMER messages sent by the system in a given period. The first problem encountered with this implementation is that you need a window to receive the notifications, so you cannot use it in console applications. In addition, messaging-based implementations are not accurate, and the situation can be even worse if your application is busy processing other messages.

An important improvement over Win32-based timers is the creation of a different thread that sleeps a specified time and notifies a callback function in .NET. With this, our timer does not need the Microsoft Windows® messaging system, so it's more accurate and can be used in console-based applications. The following code shows a possible implementation of this technique:

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");
   }
}

This code is commonly used in Win32 applications. Each timer creates a separate thread that waits the specified time, calling after that a callback function. As you can see, the cost of this implementation is very high; if our application uses several timers, the number of threads increases with them.

Now that we have .NET providing a thread pool, we can change the waiting function to a request to the pool. Even if this is perfectly valid and it would make the performance better, we will encounter two problems:

  • If the pool is full (all its threads are being used), the request waits in the queue and the timer is no longer accurate.
  • If several timers are created, the thread pool is busy waiting for them to expire.

To avoid these problems, the .NET Framework thread pool offers the possibility of time-dependent requests. With this functionality, we can have hundreds of timers without using any thread—it's the pool itself that will process the request once the timer expires.

This feature is available in two different classes:

  • System.Threading.Timer

    A simple version of a timer; it allows the developer to specify a delegate for its periodic execution on the pool.

  • System.Timers.Timer

    A component version of System.Threading.Timer; it can be inserted in a form and allows the developer to specify the executed function in terms of events.

It's important to understand the differences between the aforementioned two classes and another one named System.Windows.Forms.Timer. This class wraps the counters we had in Win32 based on Windows messages. Use this class only if you do not plan to develop a multithreaded application.

For the next example, we will use the System.Threading.Timer class, the simplest implementation of a timer. We only need the constructor, defined as follows:

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

With the first parameter (callback), we can specify the function that we want to execute periodically; the second parameter, state, is a generic object passed to the function; the third parameter, dueTime, is the delay until the counter is started; and last parameter, period, is the number of milliseconds between executions.

The below example creates two timers, timer1 and timer2:

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);
   }
}

The output will be the following:

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

As you can see, all the functions associated with both timers are executed on the same thread (ID = 2), minimizing the resources used by the application.

Execution Based on Synchronization Objects

In addition to timers, the .NET thread pool allows the execution of functions based upon synchronization objects. To share resources among threads within the multithreaded environment, we need to use .NET synchronization objects.

If we did not have a pool, threads would block waiting for the event to be signaled. As I mentioned before, this increases the total number of threads in the application, as the result it requires additional system resources and CPU.

The thread pool allows us to queue requests that will only be executed when a specified synchronization object is signaled. While this does not happen, the function does not need any thread, so optimization is assured. The ThreadPool class offers the following method:

public static RegisteredWaitHandle RegisterWaitForSingleObject(
   WaitHandle waitObject,
   WaitOrTimerCallback callBack,
   object state,
   int millisecondsTimeOutInterval,
   bool executeOnlyOnce);

The first parameter, waitObject, can be any object derived from WaitHandle:

  • Mutex
  • ManualResetEvent
  • AutoResetEvent

As you can see, only system synchronization objects can be used—that is, objects derived from WaitHandle—and you cannot use any other synchronization mechanism, like monitors or reader-writer locks.

The rest of the parameters allow us to specify a function that will be executed once the object is signaled (callBack); a state that will be passed to this function (state); the maximum time that the pool waits for the object (millisecondsTimeOutInterval) and a flag indicating if the function has to be executed once or whenever the object is signaled (executeOnlyOnce). The delegate declaration used for the callback function is the following:

delegate void WaitOrTimerCallback(
   object state,
   bool timedOut);

This function is called if the timedOut parameter set with the maximum time expires without the synchronization object being signaled.

The following example uses a manual event and a Mutex to signal the execution of a function on the thread pool:

class MainApp
{
   static void Main(string[] args)
   {
      ManualResetEvent evt = new ManualResetEvent(false);
      Mutex mtx = new Mutex(true);

      ThreadPool.RegisterWaitForSingleObject(evt,
         new WaitOrTimerCallback(PoolFunc),
         null, Timeout.Infinite, true);
      ThreadPool.RegisterWaitForSingleObject(mtx,
         new WaitOrTimerCallback(PoolFunc),
         null, Timeout.Infinite, true);

      for(int i=1;i<=5;i++)
      {
         Console.Write("{0}...", i);
         Thread.Sleep(1000);
      }
      Console.WriteLine();
      evt.Set();
      mtx.ReleaseMutex();

      Console.ReadLine();
   }
   static void PoolFunc(object obj, bool TimedOut)
   {
      Console.WriteLine("Synchronization object signaled, Thread: {0} Is pool: {1}", 
         Thread.CurrentThread.GetHashCode(),
         Thread.CurrentThread.IsThreadPoolThread);
   }
}

The output will show again that both functions will be executed on the pool and on the same thread:

1...2...3...4...5...
Synchronization object signaled, Thread: 6 Is pool: True
Synchronization object signaled, Thread: 6 Is pool: True

Asynchronous I/O Operations

The most common scenario for a thread pool is I/O (Input/Output) operations. Most applications need to wait for disk reads, data sent to sockets, Internet connections and so on. All of these operations have something in common, that is they do not require CPU time while they are performed.

.NET Framework offers in all of its I/O classes the possibility of performing operations asynchronously. When the operation is finished, the specified function is executed on the thread pool. The performance with this feature can be much better, especially if we have several threads performing I/O operations, as in most of the server-based applications.

In this first example, we will write a file asynchronously to the hard drive. Take a look to the FileStream constructor used:

public FileStream(
   string path,
   FileMode mode,
   FleAccess access,
   FleShare share,
   int bufferSize,
   bool useAsync);

The last parameter is the interesting one. We should set useAsync for our file to perform asynchronous operations. If we do not do this, even if we can use asynchronous functions, they are executed on the calling thread blocking its execution.

The following example illustrates a file write with the FileStream BeginWrite method, specifying a callback function that will be executed on the thread pool once the operation finishes. Notice that we can access the IAsyncResult interface any time, which can be used to know the current status of the operation. We use its CompletedSynchronously property to indicate whether the operation is performed asynchronously, and the IsCompleted property to set a flag when the operation finishes. IAsyncResult offers many others interesting properties, such as AsyncWaitHandle, a synchronization object that will be signaled once the operation is performed.

class MainApp
{
   static void Main()
   {
      const string fileName = "temp.dat";
      FileStream fs;
      byte[] data = new Byte[10000];
      IAsyncResult ar;

      fs = new FileStream(fileName, 
         FileMode.Create, 
         FileAccess.Write, 
         FileShare.None, 
         1, 
         true);
      ar = fs.BeginWrite(data, 0, 10000,
         new AsyncCallback(UserCallback), null);
      Console.WriteLine("Main thread:{0}",
         Thread.CurrentThread.GetHashCode());
      Console.WriteLine("Synchronous operation: {0}",
         ar.CompletedSynchronously);
      Console.ReadLine();
   }
   static void UserCallback(IAsyncResult ar)
   {
      Console.Write("Operation finished: {0} on thread ID:{1}, is pool: {2}", 
         ar.IsCompleted, 
         Thread.CurrentThread.GetHashCode(), 
         Thread.CurrentThread.IsThreadPoolThread);
   }
}

The output will show us that the operation is performed asynchronously; once the operation is finished, the user function is executed on the thread pool.

Main thread:9
Synchronous operation: False
Operation finished: True on thread ID:10, is pool: True

In the case of sockets, using the thread pool is even more important because its I/O operations are usually slower than disks. The procedure is the same as before, the Socket class offers methods to perform any operation asynchronously:

  • BeginReceive
  • BeginSend
  • BeginConnect
  • BeginAccept

If your server application uses sockets to communicate with the other clients, always use these methods. This way, instead of needing a thread for each connected client, all the operations are performed asynchronously on the thread pool.

The following example uses another class that supports asynchronous operations, HttpWebRequest. With this class, we can establish a connection with a Web server on the Internet. The method used now is called BeginGetResponse, but in this case there's an important difference. In the last example, we wrote a file to disk and we didn't need any results from the operation. However, we now need the response from the Web server when the operation is finished. In order to retrieve this information, all the .NET Framework classes that offer I/O operations have a pair of methods for each one. In the case of HttpWebRequest, the pair is BeginGetResponse and EndGetResponse.

With the End version, we can retrieve the result of the operation. In our case, EndGetResponse will return the response from the Web server. Even if EndGetResponse can be called any time, in our example we do it using the callback function, just when we know that the asynchronous request is done. If we call EndGetResponse before that, the call will block until the operation is finished.

In the following example, we send a request to Microsoft Web and show the size of the received response:

class MainApp
{
   static void Main()
   {
      HttpWebRequest request;
      IAsyncResult ar;

      request = (HttpWebRequest)WebRequest.CreateDefault(
         new Uri("http://www.microsoft.com"));
      ar = request.BeginGetResponse(new AsyncCallback(PoolFunc), request);
      Console.WriteLine("Synchronous: {0}", ar.CompletedSynchronously);
      Console.ReadLine();
   }
   static void PoolFunc(IAsyncResult ar)
   {
      HttpWebRequest request;
      HttpWebResponse response;

      Console.WriteLine("Response received on pool: {0}",
         Thread.CurrentThread.IsThreadPoolThread);
      request = (HttpWebRequest)ar.AsyncState;
      response = (HttpWebResponse)request.EndGetResponse(ar);
      Console.WriteLine("  Response size: {0}",
         response.ContentLength);
   }
}

The output will show at the beginning the following message, indicating that the operation is being performed asynchronously:

Synchronous: False

After a while, when the response is received, the following output will be shown:

Response received on pool: True
   Response size: 27331

As you can see, once the response is received, the callback function is executed on the thread pool.

Monitoring the Pool

The ThreadPool class offers two methods used to query the status of the pool. With the first one we can retrieve the number of free threads:

public static void GetAvailableThreads(
   out int workerThreads,
   out int completionPortThreads);

As you can see there are two different kinds of threads:

  • WorkerThreads

    The worker threads are part of the standard system pool. They are standard threads managed by the .NET Framework and most of the functions are executed on them, specifically user requests (QueueUserWorkItem method), functions based on synchronization objects (RegisterWaitForSingleObject method), and timers (Timer classes).

  • CompletionPortThreads

    This kind of thread is used for I/O operations, whenever is possible. Windows NT, Windows 2000, and Windows XP offer an object specialized on asynchronous operations, called IOCompletionPort. With the API associated with this object we can launch asynchronous I/O operations managed with a thread pool by the system, in an efficient way and with few resources. However, Windows 95, Windows 98, and Windows Me have some limitations with asynchronous I/O operations. For example, IOCompletionPorts functionality is not offered and asynchronous operations on some devices, such as disks and mail slots, cannot be performed. Here you can see one of the greatest features of the .NET Framework: compile once and execute on multiple systems. Depending on the target platform, the .NET Framework will decide to use the IOCompletionPorts API or not, maximizing the performance and minimizing the resources.

This section includes an example using the socket classes. In this case we are going to establish a connection asynchronously with the local Web server and send a Get request. With this, we can easily identify both kinds of threads.

using System;
using System.Threading;
using System.Net;
using System.Net.Sockets;
using System.Text;

namespace ThreadPoolTest
{
   class MainApp
   {
      static void Main()
      {
         Socket s;
         IPHostEntry hostEntry;
         IPAddress ipAddress;
         IPEndPoint ipEndPoint;
         
         hostEntry = Dns.Resolve(Dns.GetHostName());
         ipAddress = hostEntry.AddressList[0];
         ipEndPoint = new IPEndPoint(ipAddress, 80);
         s = new Socket(ipAddress.AddressFamily,
            SocketType.Stream, ProtocolType.Tcp);
         s.BeginConnect(ipEndPoint, new AsyncCallback(ConnectCallback),s);
         
         Console.ReadLine();
      }
      static void ConnectCallback(IAsyncResult ar)
      {
         byte[] data;
         Socket s = (Socket)ar.AsyncState;
         data = Encoding.ASCII.GetBytes("GET /\n");

         Console.WriteLine("Connected to localhost:80");
         ShowAvailableThreads();
         s.BeginSend(data, 0,data.Length,SocketFlags.None,
            new AsyncCallback(SendCallback), null);
      }
      static void SendCallback(IAsyncResult ar)
      {
         Console.WriteLine("Request sent to localhost:80");
         ShowAvailableThreads();
      }
      static void ShowAvailableThreads()
      {
         int workerThreads, completionPortThreads;

         ThreadPool.GetAvailableThreads(out workerThreads,
            out completionPortThreads);
         Console.WriteLine("WorkerThreads: {0}," + 
            " CompletionPortThreads: {1}",
            workerThreads, completionPortThreads);
      }
   }
}

If you run this program on Microsoft Windows NT, Windows 2000, or Windows XP, you will see the following output:

Connected to localhost:80
WorkerThreads: 24, CompletionPortThreads: 25
Request sent to localhost:80
WorkerThreads: 25, CompletionPortThreads: 24

As you can see, connecting with a socket uses a worker thread, while sending the data uses a CompletionPort. This following sequence is followed:

  1. We get the local IP address and connect to it asynchronously.
  2. Socket performs the asynchronous connection on a worker thread, since Windows IOCompletionPorts cannot be used to establish connections on sockets.
  3. Once the connection is established, the Socket class calls the specified function ConnectCallback. This callback shows the number of available threads on the pool, this way we can see that it is being executed on a worker thread.
  4. An asynchronous request is sent from the same function ConnectCallback. We use for this the BeginSend method, after encoding the Get / request in ASCII code.
  5. Send/receive operations on a socket can be performed asynchronously with an IOCompletionPort, so when our request is done, the callback function SendCallback is executed on a CompletionPort thread. We can check this because the function itself shows the number of available threads and we can see that only those corresponding to CompletionPorts have been decreased.

If we run the same code on a Windows 95, Windows 98, or Windows Me platform, the result will be the same on the connection, but the request will be sent on a worker thread, instead of a CompletionPort. The important thing you should learn about this is that the Socket class always uses the best available mechanism, so you can develop your application without taking into account the target platform.

You have seen in the example that the maximum number of available threads is 25 for each type (exactly 25 times the number of processors in your machine). We can retrieve this number using the GetMaxThreads method:

public static void GetMaxThreads(
   out int workerThreads,
   out int completionPortThreads);

Once this maximum value is reached, no new threads are created and requests are queued. If you see all the methods declared in the ThreadPool class, you will notice that none of them allows us to change this maximum value. As we mentioned before, the thread pool is a unique shared resource per process; that is why it is impossible for an application domain to change its configuration. Imagine the consequences if a third-party component changes the maximum number of threads on the pool to 1, the entire application can stop working and even other application domains in the same process will be affected. For the same reason, systems that host the common language runtime do have the possibility to change the configuration. For example, Microsoft ASP.NET allows the Administrator to change the maximum number of threads available on the pool.

Deadlocks

Before starting to use the thread pool in your applications you should know one additional concept: deadlocks. A bad implementation of asynchronous functions executed on the pool can make your entire application hang.

Imagine a method in your code that needs to connect via socket with a Web server. A possible implementation is opening the connection asynchronously with the Socket class' BeginConnect method and wait for the connection to be established with the EndConnect method. The code will be as follows:

class ConnectionSocket
{
   public void Connect()
   {
      IPHostEntry ipHostEntry = Dns.Resolve(Dns.GetHostName());
      IPEndPoint ipEndPoint = new IPEndPoint(ipHostEntry.AddressList[0],
         80);
      Socket s = new Socket(ipEndPoint.AddressFamily, SocketType.Stream,
         ProtocolType.Tcp);
      IAsyncResult ar = s.BeginConnect(ipEndPoint, null, null);
      s.EndConnect(ar);
   }
}

So far, so good—calling BeginConnect makes the asynchronous operation execute on the thread pool and EndConnect blocks waiting for the connection to be established.

What happens if we use this class from a function executed on the thread pool? Imagine that the size of the pool is just two threads and we launch two asynchronous functions that use our connection class. With both functions executing on the pool, there is no room for additional requests until the functions are finished. The problem is that these functions call our class' Connect method. This method launches again an asynchronous operation on the thread pool, but since the pool is full, the request is queued waiting any thread to be free. Unfortunately, this will never happen because the functions that are using the pool are waiting for the queued functions to finish. The conclusion: our application is blocked.

We can extrapolate this behavior for a pool of 25 threads. If 25 functions are waiting for an asynchronous operation to be finished, the situation becomes the same and the deadlock occurs again.

In the following fragment of code we have included a call to the last class to reproduce the problem:

class MainApp
{
   static void Main()
   {
      for(int i=0;i<30;i++)
      {
         ThreadPool.QueueUserWorkItem(new WaitCallback(PoolFunc));
      }
      Console.ReadLine();
   }

   static void PoolFunc(object state)
   {
      int workerThreads,completionPortThreads;
      ThreadPool.GetAvailableThreads(out workerThreads,
         out completionPortThreads);
      Console.WriteLine("WorkerThreads: {0}, CompletionPortThreads: {1}", 
         workerThreads, completionPortThreads);

      Thread.Sleep(15000);
      ConnectionSocket connection = new ConnectionSocket();
      connection.Connect();
   }
}

If you run the example, you see how the threads on the pool are decreasing until the available threads reach 0 and the application stops working. We have a deadlock.

In general, a deadlock can appear whenever a pool thread waits for an asynchronous function to finish. If we change the code so that we use the synchronous version of Connect, the problem will disappear:

class ConnectionSocket
{
   public void Connect()
   {
      IPHostEntry ipHostEntry = Dns.Resolve(Dns.GetHostName());
      IPEndPoint ipEndPoint = new IPEndPoint(ipHostEntry.AddressList[0], 80);
      Socket s = new Socket(ipEndPoint.AddressFamily, SocketType.Stream,
         ProtocolType.Tcp);
      s.Connect(ipEndPoint);
   }
}

If you want to avoid deadlocks in your applications, do not ever block a thread executed on the pool that is waiting for another function on the pool. This seems to be easy, but keep in mind that this rule implies two more:

  • Do not create any class whose synchronous methods wait for asynchronous functions, since this class could be called from a thread on the pool.
  • Do not use any class inside an asynchronous function if the class blocks waiting for asynchronous functions.

If you want to detect a deadlock in your application, check the available number of threads on the thread pool when your system is hung. The lack of available threads and CPU utilization near 0% are clear symptoms of a deadlock. You should monitor your code to identify where a function executed on the pool is waiting for an asynchronous operation and remove it.

About Security

If you take a look to the ThreadPool class, you see that there are two methods that we haven't seen yet: UnsafeQueueUserWorkItem and UnsafeRegisterWaitForSingleObject. To fully understand the purpose of these methods we have to remember first how code security works in the .NET Framework.

Security in Windows is focused on resources. The operating system itself allows setting permissions on files, users, registry keys or any other resource of the system. This approach is perfect for applications trusted by the user, but it has limitations when the user does not trust the applications he uses, for example those downloaded from the Internet. In this case, once the user installs the application, it can perform any operation allowed by his permissions. For example, if the user is able to delete all the shared files of his company, any application downloaded from the Internet could do it, too.

.NET offers security applied to the application, not to the user. This means that, within the limit of the user's permissions, we can restrict resources to any execution unit (Assembly). With the MMC snap-in, we can define groups of assemblies by several conditions and set different security policies for each group. A typical example of this is restricting the disk access to applications downloaded from the Internet.

For this to work, the .NET Framework must maintain a calling stack between different assemblies. Imagine an application that has no permission to access to disk, but it calls a class library with full access to the system. When the second assembly performs an operation to disk, the set of permissions associated with it allows the action, but the permissions applied to the calling assembly do not. The .NET Framework must check not only the current assembly permissions, but also the permissions applied to the entire calling stack. This stack is highly optimized, but they add an overhead to calls between functions of different assemblies.

UnsafeQueueUserWorkItem and UnsafeRegisterWaitForSingleObject are equivalent functions to QueueUserWorkItem and RegisterWaitForSingleObject, but the unsafe versions do not maintain the calling stack for the asynchronous functions they perform. So, unsafe versions are faster but the callback functions will be executed only with the current assembly security policies, losing all the permissions applied to the calling stack of assemblies.

My recommendation is that you should use these unsafe functions with extreme caution and only in situations where the performance is very important and the security is controlled. For example, you can use unsafe versions if you are building an application without the possibility of being called from another assembly or where the policies applied to it allows only another well-known assembly to use it. You should never use these methods if you are developing a class library that can be used by any third-party application, because they could use your library to gain access to limited resources of the system.

In the following example, you can see the risk of using UnsafeQueueUserWorkItem. We will build two separated assemblies, in the first one we will use the thread pool to create a file and we will export a class so that this operation can be performed from another assembly:

using System;
using System.Threading;
using System.IO;

namespace ThreadSecurityTest
{
   public class PoolCheck
   {
      public void CheckIt()
      {
         ThreadPool.QueueUserWorkItem(new WaitCallback(UserItem), null);
      }

      private void UserItem(object obj)
      {
         FileStream fs = new FileStream("test.dat", FileMode.Create);
         fs.Close();
         Console.WriteLine("File created");
      }
   }
}

The second assembly references the first one and it uses the CheckIt method to create the file:

using System;

namespace ThreadSecurityTest
{
   class MainApp
   {
      static void Main()
      {
         PoolCheck pc = new PoolCheck();
         pc.CheckIt();
         Console.ReadLine();
      }
   }
}

Compile both assemblies and run the main application. By default, your system is configured to allow disk operations to be performed, so the application works perfectly and the file is generated:

File created

Now open the Microsoft .NET Framework configuration—in this case, to simplify the example, we create only a code group associated with the main application. To do this, expand the Runtime Security Policy/Machine/Code Groups/All_Code node and add a new group called ThreadSecurityTest. In the wizard, select the Hash condition and import the hash of our application. Now set an Internet permission level and force it with the This policy level will only have the permissions from the permission set associated with this code group option.

Run the application again and see what happens:

Unhandled Exception: System.Security.SecurityException: Request for the 
   permission of type System.Security.Permissions.FileIOPermission, 
      mscorlib, Version=1.0.3300.0, Culture=neutral, 
         PublicKeyToken=b77a5c561934e089 failed.
...

Our policy has worked and the application cannot create the file. Notice that this is possible because the .NET Framework is maintaining the calling stack for us, since the library that created the file had full access to the system.

Now change the code of the library and use the UnsafeQueueUserWorkItem method instead of QueueUserWorkItem. Compile the assembly again and run the main application. The output will now be:

File created

Even if our application does not have enough permission to access the disk, we have created a library that exposes this functionality to the whole system without maintaining the calling stack. Remember the golden rule: Use unsafe functions only when your code cannot be called from other applications or when you restrict the access to well-known assemblies.

Conclusion

In this article, we have seen why we need a thread pool to optimize the resources and CPU usage in our server applications. We have studied how a thread pool has to be implemented; taking into account many factors like percentage used of CPU, queued requests, or number of processors in the system.

The .NET Framework offers a fully functional implementation of thread pool ready to be used by our application and tightly integrated with the rest of classes of the .NET Framework. This pool is highly optimized, minimizing the privileged CPU time utilization with minimal resources, and is always adapted to the target operating system.

Because of the integration with the Framework, most of the classes offered by it use the thread pool internally, offering to the developers a centralized place to manage and monitor the pool in their applications. Third-party components are encouraged to use the thread pool; this way their clients can use all the functionality provided, allowing the execution of user functions, timers, I/O operations or synchronization objects.

If you are developing server applications, whenever possible use the thread pool in your request processing system. Otherwise, if your development is a library that can be used by a server application, always offer the possibility of asynchronous processing on the system thread pool.

For More Information

  • .NET Framework Class Library: ThreadPool Class
  • .NET Framework Developer's Guide: Threading
  • Multithreaded Programming with Visual Basic .NET
  • .NET Framework Developer's Guide: Asynchronous File I/O
  • .NET Framework Developer's Guide: Including Asynchronous Calls
  • .NET Framework Developer's Guide: Accessing the Internet

posted @ 2004-08-26 10:44 停留.Xp 阅读(1231) 评论(0) 编辑

今天去火车站送女朋友了

摘要: 今天天气很不错,本来不应该去送女朋友。但是我总是感觉不去送会后悔,或许自己有强迫症。6:30就起床了,然后打车去女朋友家。收拾好行李,匆忙地就去了火车站。这是第一次去送女朋友,我知道,以后有很多次。很多次昨天看了仔细看了一下delegate,感觉非常不错也:),这里提供一个简单例子:此处介绍的示例是建立在事件和委托和引发事件中讨论的多个部分的基础上的。本示例显示了如何从您的类引发事件以及如何处理事...阅读全文

posted @ 2004-08-26 09:03 停留.Xp 阅读(1143) 评论(0) 编辑