Multithreading in ASP.NET Applications

Learn how you can improve application responsiveness

Performance and responsiveness are key factors for an application's success. Multithreading, which you can use to boost application responsiveness, can overcome potential problems that come up due to long-running tasks or CPU-intensive operations. In this article, I explain how you can use multithreading in ASP.NET applications. To begin, let’s take a look at related concepts and terminologies.

Threads, Processes, and Tasks—What Are They and How Do They Differ?
A thread—also called a lightweight process—is the smallest individually schedulable entity within a process, and the path of execution within a process. A process on the other hand is the running instance of a program. An application’s main thread is the application thread. All other threads that you create in the application are called worker threads. A task is an enhanced form of a thread.

Multithreading, Multitasking, and Multiprocessing
Multithreading is the OS’s ability to have at the same point in time multiple threads in memory that switch between tasks in such a way as to provide a pseudo parallelism, as if all the threads are running simultaneously. Note that switching between threads involves a context switch, which in turn involves saving the current thread’s state, flushing the CPU, and handing control of the CPU to the next thread in the queue.  

Multithreading can be of the following types:
•    Serial – supported by DOS OS
•    Cooperative – supported by Windows 3.11
•    Preemptive – supported by Windows 9x, NT, XP, and so on

Multitasking is an enhanced form of multithreading. Note that all multitasking operating systems are also multithreading in nature and the reverse is also implied.  Multiprocessing is a concept that involves multiple processors, with each processor executing a particular thread at any point in time. Note that you can have only one thread in execution state in a single processor because a single CPU can execute only one thread at a time.

Advantages and Disadvantages of Multithreading
Using multithreading in applications boosts the application’s responsiveness. You can use multithreading to enhance the CPU and memory utilization in your system and provide support for concurrency. However, it’s difficult to test and debug applications that use multithreading. Moreover, implementing multithreading applications is quite complex.

Thread States
A thread can have one of the following states:

• Ready or Run-able state—A thread in this state has all resources it needs, excluding the processor. It awaits its turn in the ready queue (a queue of ready-to-use threads) to be allocated the processor.
• Running state—A thread in this state has all resources it needs, inclusive of the processor. Hence, a thread in this state is said to be in the running or executing state. Note that in a single processor, you can at any point in time have only one thread in the running state.
• Wait state—A thread in this state waits for the IO to be complete. Once the IO activity for the thread is complete, the thread is scheduled again to the ready queue.

The ThreadState enum in the System.Threading namespace in C# contains the various supported thread states in .NET.  The following are the members of the ThreadState enum:

• Unstarted
• Running
• Background
• StopRequested
• Suspended
• SuspendRequested
• WaitSleepJoin
• Aborted
• AbortRequested
• Stopped

Using the System.Threading.Thread Class
You can use the Thread class belonging to the System.Threading namespace to create, use, and manage threads in ASP.NET applications. To create and start a new thread, you need to create an instance of the Thread class and then call its start method. Here’s an example:

 

Thread thread = new Thread(new ThreadStart(DoWork));
thread.Start();

Notice the parameter DoWork in the call to the ThreadStart constructor in the code above. The method DoWork is a thread method. A thread method is one that contains the actual code that the thread should execute. It should have no parameters and should have a return type of void. Here’s what a typical thread method looks like:

 

public void DoWork()
\\{
while(some condition)
\\{
//Some code
\\}
\\}



When you start a thread using the Start() method on the thread object, the thread is scheduled to the ready state (i.e., it’s placed in the ready queue). Here it awaits its turn to be allocated the processor. The thread would be allocated the processor as and when its turn comes. The OS takes care of scheduling threads from the ready state to the running state; you have no control over this.

Suspending and Resuming Threads
You can suspend a thread that is already running by making a call to the Suspend() method on the thread object. Note that the runtime doesn’t suspend a thread immediately after you make a call to the Suspend() method. The thread is placed in the suspended state only after a safe point is achieved. A safe point is a point at which suspension is possible and this point is decided by the runtime only. Here’s how you can use the Suspend() method to request a thread to be suspended:

 

if (thread.ThreadState 

ThreadState.Running )
\\{
thread.Suspend();
\\}

You can revoke the suspension of the thread later by making a call to the Resume() method on the thread object. Here’s an example:

 

if (thread.ThreadState ThreadState.Suspended ) 
\\{
thread.Resume();
\\}

You can also put a thread in the sleep state by making a call to the Sleep() method on the thread object. The following code makes the thread sleep for 10 seconds:

 

Thread.Sleep(10000);

You can also put a thread to sleep infinitely using the following code:

 

Thread.Sleep(TimeSpan.Infinite);

Joining Threads
You can use the Join() method on a thread to allow it to wait until another thread has completed its activity. Here’s an example that illustrates how you can make a worker thread wait until the other thread has completed its work:

 

if(Thread.CurrentThread.GetHashCode()!= thread.GetHashCode())
\\{
thread.Join();
\\}

Terminating Threads
To terminate a thread you can use the Abort() method, which stops a running thread prematurely. This method raises a ThreadAbortException. Here’s an example:

 

if (thread.IsAlive) 
\\{
thread.Abort();
\\}

Thread Priorities
The priority of a thread is defined by using the ThreadPriority enum. The following are the possible values:

• Highest
• AboveNormal
• Normal
• BelowNormal
• Normal

Use Threads Pools for Efficient Thread Management
A thread pool is a pool of ready-to-use threads. Instead of creating a thread as and when one is needed and destroying it once done, an application can request a thread from the pool of ready-to-use threads. Once the thread completes its activity, it’s returned to the thread pool instead of being destroyed. In essence, you no longer have the overhead of unnecessary thread creation and termination; thus the application’s performance is boosted.

The ThreadPool class in the System.Threading namespace provides support for creating and managing thread pools. Note that, by default, a thread pool contains 25 threads. Also, there would be one thread pool per process at any point in time. To consume a thread from the managed thread pool, you can use the ThreadPool.QueueUserWorkItem static method:

 

ThreadPool.QueueUserWorkItem( new WaitCallback(DoWork));

ASP.NET uses a process-wide CLR thread pool to service requests. By default, Its value is set to 25 worker and 25 I/O threads. You can also configure this pool in the processModel section of the machine.config file:

 

<processModel enable="true" 
  ...
 maxWorkerThreads="25"
 maxIoThreads="25" />

Similarly, you can change the count of the minimum number of worker threads in the machine.config file. Here’s an example:

<configuration>
  <system.web>
    <processModel minWorkerThreads="60" />
   </system.web>
</configuration>

Advantages and Disadvantages of Thread Pools
The most important benefit that you get in using a thread pool is that the overhead of creating, managing, and terminating threads is eliminated and hence the KLOC is also reduced. Using a thread pool also cuts down on the number of threads being used by the system. The major downside in using a thread pool is that you have no control over the thread’s identity or state information. Also, the working of a thread pool is non-deterministic; you have no control over when a thread submitted to a thread pool would actually execute.



Comparing Thread Pools and Async Pages
Async ASP.NET web pages are a better choice than thread pools, especially when the number of concurrent requests exceeds the number of request processing threads available in the thread pool. The number of request processing threads in the thread pool is limited. If the number of concurrent requests exceed the number of available threads in the thread pool, successive requests can be delayed or some may time out, resulting in the "503 Server Unavailable" error from the web server.

Async ASP.NET web pages return the request-processing threads to the thread pool immediately. After processing is complete, a new request-processing thread returns the response to the client. Note that an asynchronous page is not faster than a synchronous page; rather, there is a slight performance penalty to switch between threads. The advantage in using async pages is that the web pages that have long-running or time-consuming operations can be served faster by the web server—an improvement on scalability of the application as a whole.

You can make an ASP.NET web page work asynchronously using the @Page directive’s Async attribute. Just set it to true as shown below:

 

<%@ Page Language="C#" Async="true" %>

A detailed discussion of Async ASP.NET pages and how they can be used in ASP.NET is beyond the scope of this article. But, in the sections that follow, I’ll discuss how you can use thread pools in ASP.NET as a better choice over using the Thread class in the System.Threading namespace for implementing ASP.NET applications that use multithreading.

Using Thread Pools in ASP.NET
To use a thread pool, you need to make use of the System.Threading.ThreadPool class. As noted, by default the class allows you to use 25 threads. The ThreadPool class contains a list of static methods that you can use to manage the thread pool. Here’s the list:

• GetMaxThreads—This method returns the maximum number of thread pool worker threads and asynchronous I/O threads allowed in the thread pool.
• GetMinThreads—This method returns the number of idle threads in the thread pool.
• GetAvailableThreads—This method returns the total number of available threads in the thread pool. The total number of available threads in the thread pool is given by the difference between the maximum number of threads in the thread pool and the number of threads currently active.

Initializing the Thread Pool
You can also use the SetMaxThreads and the SetMinThreads methods to manage the thread pool. The SetMaxThreads method sets the maximum number of worker threads and I/O completion threads allowed in the thread pool; the SetMinThreads method sets the minimum number of idle threads in the thread pool. The following method shows how you can use these methods to initialize your thread pool before you use it in your application.

 

bool InitializeThreadPool(int maxWorkerThreads, int maxCompletionPortThreads, int minWorkerThreads, int minCompletionPortThreads)
\\{
return (ThreadPool.SetMaxThreads(maxWorkerThreads, maxCompletionPortThreads) && ThreadPool.SetMinThreads(minWorkerThreads, minCompletionPortThreads));
\\}

You can call the above method as shown below:

 

if (!InitializeThreadPool(150, 200, 50, 50))
\\{
Response.Write("
Error in InitializeThreadPool method");
\\}

Consuming a Thread in the ThreadPool
Consuming a thread from the thread pool is simple. Just call the QueueUserWorkItem static method of the ThreadPool class. Here’s an example:

 

for (int i = 0; i < 10; i++)
\\{
ThreadPool.QueueUserWorkItem(new WaitCallback(DoWork));
\\}

Here’s the source code of the DoWork method:

 

 void DoWork(object state)
\\{
Thread.Sleep(10000);
\\}

The QueueUserWorkItem method returns a boolean value. If it succeeds, it returns true; otherwise it returns false. You can use this value to detect whether or not the thread processing has been successful. Figure 1 shows an example.

Displaying the Statistics of the Threads in the ThreadPool
You can use the IsThreadPoolThread property of the Thread class to check whether a thread belongs to the managed thread pool thread. The method in Figure 2 displays information about any thread object passed to it as a parameter.

The method in Figure 3 illustrates how you can retrieve the status of the threads in the thread pool. Figure 4 sums up what we’ve learned and illustrates the complete source of a program that, when executed, would produce an output similar to what is shown in Figure 5.


Joydip Kanjilal ([email protected]) is a Microsoft MVP in ASP.NET. He has more than 12 years of industry experience in IT with more than six years in Microsoft .NET and its related technologies.

Hide comments

Comments

  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Publish