Improving ASP.NET Performance Part3: Threading

In our last two previous series of articles we introduced our new series about ASP.NET performance, ASP.NET Design Considerations and ASP Code Access Security. In part 3 of this series we’ll discuss Threading.

Threading Explained

ASP.NET processes requests by using threads from the .NET thread pool. The thread pool maintains a pool of threads that have already incurred the thread initialization costs. Therefore, these threads are easy to reuse. The .NET thread pool is also self-tuning. It monitors CPU and other resource utilization, and it adds new threads or trims the thread pool size as needed. You should generally avoid creating threads manually to perform work. Instead, use threads from the thread pool. At the same time, it is important to ensure that your application does not perform lengthy blocking operations that could quickly lead to thread pool starvation and rejected HTTP requests.

Reducing Contention

The formula for reducing contention can give you a good start for tuning the ASP.NET thread pool. Consider using the Microsoft recommended settings, outlined below:

  • Set maxconnection to 12 * # of CPUs. This setting controls the maximum number of outgoing HTTP connections that you can initiate from a client. In this case, ASP.NET is the client. Set maxconnection to 12 * # of CPUs.
  • Set maxIoThreads to 100. This setting controls the maximum number of I/O threads in the .NET thread pool. This number is automatically multiplied by the number of available CPUs. Set maxloThreads to 100.
  • Set maxWorkerThreads to 100. This setting controls the maximum number of worker threads in the thread pool. This number is then automatically multiplied by the number of available CPUs. Set maxWorkerThreads to 100.
  • Set minFreeThreads to 88 * # of CPUs. This setting is used by the worker process to queue all the incoming requests if the number of available threads in the thread pool falls below the value for this setting. This setting effectively limits the number of requests that can run concurrently to maxWorkerThreads – minFreeThreads. Set minFreeThreads to 88 * # of CPUs. This limits the number of concurrent requests to 12 (assuming maxWorkerThreads is 100).
  • Set minLocalRequestFreeThreads to 76 * # of CPUs. This setting is used by the worker process to queue requests from localhost (where a Web application sends requests to a local Web service) if the number of available threads in the thread pool falls below this number. This setting is similar to minFreeThreads but it only applies to localhost requests from the local computer. Set minLocalRequestFreeThreads to 76 * # of CPUs.

The recommendations we give in this section are a starting point. You should do some testing to determine the appropriate settings for your scenario. If your ASPX Web page makes multiple calls to Web services on a per-request basis, apply the recommendations.

The recommendation to limit the ASP.NET runtime to 12 threads for handling incoming requests is most applicable for quick-running operations. The limit also reduces the number of context switches. If your application makes long-running calls, first consider the design alternatives presented in the “Avoid Blocking on Long-Running Tasks” section. If the alternative designs cannot be applied in your scenario, start with 100 maxWorkerThreads, and keep the defaults for minFreeThreads. This ensures that requests are not serialized in this particular scenario. Next, if you see high CPU utilization and context-switching when you test your application, test by reducing maxWorkerThreads or by increasing minFreeThreads.

The following occurs if the formula has worked:

· CPU utilization increases.

· Throughput increases according to the ASP.NET Applications\Requests/Sec performance counter.

· Requests in the application queue decrease according to the ASP.NET Applications/Requests In Application Queue performance counter.

If using the recommended settings does not improve your application performance, you may have a CPU bound scenario.

Threading Guidelines

This section discusses guidelines that you can use to help improve threading efficiency in ASP.NET.

Tune the Thread Pool by Using the Formula to Reduce Contention

If you have available CPU and if requests are queued, configure the ASP.NET thread pool. For more information about how to do this, see “Reducing Contention” in the previous section. When your application uses the common language runtime (CLR) thread pool, it is important to tune the thread pool correctly. Otherwise, you may experience contention issues, performance problems, or possible deadlocks. Your application may be using the CLR thread pool if the following conditions are true:

·Your application makes Web service calls.

·Your application uses the WebRequest or HttpWebRequest classes to make outgoing Web requests.

·Your application explicitly queues work to the thread pool by calling the QueueUserWorkItem method.

Consider minIoThreads and minWorkerThreads for Burst Load

If your application experiences burst loads where there are prolonged periods of inactivity between the burst loads, the thread pool may not have enough time to reach the optimal level of threads. A burst load occurs when a large number of users connect to your application suddenly and at the same time. The minIoThreads and minWorkerThreads settings enable you to configure a minimum number of worker threads and I/O threads for load conditions.

Do Not Create Threads on a Per-Request Basis

Creating threads is an expensive operation that requires initialization of both managed and unmanaged resources. You should avoid manually creating threads on each client request for server-based applications such as ASP.NET applications and Web services.

Consider using asynchronous calls if you have work that is not CPU bound that can run in parallel with the call. For example, this might include disk I/O bound or network I/O bound operations such as reading or writing files, or making calls to another Web method.

You can use the infrastructure provided by the .NET Framework to perform asynchronous operations by calling the Beginsynchronous and Endsynchronous methods (where synchronous represents the synchronous method name). If this asynchronous calling pattern is not an option, then consider using threads from the CLR thread pool. The following code fragment shows how you queue a method to run on a separate thread from the thread pool.

WaitCallback methodTarget = new WaitCallback(myClass.UpdateCache);

bool isQueued = ThreadPool.QueueUserWorkItem(methodTarget);

Avoid Blocking Threads

Any operation that you perform from an ASP.NET page that causes the current request thread to block means that one less worker thread from the thread pool is available to service other ASP.NET requests. Avoid blocking threads.

Avoid Asynchronous Calls Unless You Have Additional Parallel Work

Make asynchronous calls from your Web application only when your application has additional parallel work to perform while it waits for the completion of the asynchronous calls, and the work performed by the asynchronous call is not CPU bound. Internally, the asynchronous calls use a worker thread from the thread pool; in effect, you are using additional threads.

At the same time that you make asynchronous I/O calls, such as calling a Web method or performing file operations, the thread that makes the call is released so that it can perform additional work, such as making other asynchronous calls or performing other parallel tasks. You can then wait for completion of all of those tasks. Making several asynchronous calls that are not CPU bound and then letting them run simultaneously can improve throughput.

In our next article we’ll talk about Resource Management guidelines that you can use to optimize the performance of your ASP.NET application.