ASP.NET applications already get a lot of concurrency for free. The .NET Framework load balances incoming requests among ThreadPool worker threads, striving for optimal use of available CPUs. As long as you minimize blocking in your ASP.NET page code, ASP.NET will process requests concurrently. In most cases, and in particular for Web applications with heavy usage, it is probably not necessary to introduce extra parallelism since adding more work items will only result in competition for CPU time and ultimately reduce request throughput.
Dealing with I/O bound work
If most of the work being done in an ASP.NET request is asynchronous in nature (such as I/O), doing the asynchronous work synchronously can be a huge scalability bottleneck. Solutions based on Asynchronous Programming Model (APM) and Event-based Asynchronous Pattern (EAP) have been recommended to ease this bottleneck. For an in-depth discussion on this refer to Scalable Apps with Asynchronous Programming in ASP.NET and Asynchronous Pages in ASP.NET 2.0. The article Improving ASP.NET Performance also has some good pointers to improving the scalability of your web applications.
New features in the .NET Framework 4 can also be used to make programming asynchronous pages easier. The System.Threading.Tasks.Task class (and the Task<TResult> class that derives from it) can be used to represent asynchronous operations, both classes implement IAsyncResult, and they provide capabilities for coordinating between multiple asynchronous activities. Since part of ASP.NET’s asynchronous pages support is based on the Asynchronous Programming Model (APM) pattern and IAsyncResult, Task can play a role in easing the implementation of asynchronous pages. In particular, Task is most useful if you want to structure your code with continuations, which can be useful if you have multiple stages of asynchronous activity that need to happen before the rest of the page continues execution. For more details, refer to Tasks and the Event-based Asynchronous Pattern and Tasks and the APM Pattern
Dealing with CPU intensive work
Web applications that need to perform expensive computations may still benefit from parallelism if the latency of an individual request is more important than overall request throughput. If this is the case, the new APIs for parallelism in .NET 4 such as Task Parallel Library and PLINQ can simplify writing the parallel code. When integrating parallelism into your web application, consider the following factors:
If requests are computationally cheap to process, then parallelism is probably an unnecessary overhead.
If the incoming request rate is high, then adding more parallelism will likely yield few benefits and could actually decrease performance, since the incoming rate of work may be high enough to keep the CPUs busy.
If the incoming request rate is low, then the Web application could benefit from parallelism by using the idle CPU cycles to speed up the processing of an individual request. We can use either PLINQ or TPL (either Parallel loops or the Task class) to parallelize the computation over all the processors. Note that by default, however, the PLINQ implementation in .NET 4 will tie-up one ThreadPool worker per processor for the entire execution of the query. As such, it should only be used in Web applications that see few but expensive requests.
If the incoming request rate is variable, i.e. there are long periods when request rate is low (say, at night) and then other periods when request rate is high (say, midday), we need a strategy that will dynamically adjust to the available resources. When the load is high, we don’t want to add to the contention but when the load is low, we want to use the idle resources. For this scenario, we can use TPL’s Parallel or Task constructs since they can adapt to use available resources within a process. If the server is already loaded, the Parallel loops can use as little as one worker and make forward progress. If the server is mostly free, they can grow to use as many workers as the ThreadPool can spare.
Developing libraries for ASP.NET
If you’re developing a library that uses the parallel programming features of .NET 4, you should consider whether it is going to be to be used within ASP.NET. If it is, you should consider exposing knobs from your library that enable controlling how much parallelism is employed by the library. This is particularly important for libraries that utilize PLINQ. In .NET 4, PLINQ by default uses a fixed number of workers equal to the number of logical processors. By exposing control to the consumer of the library, the consumer can specify a maximum amount of parallelism to be employed, and this value can be configured based on the environment. The number of workers PLINQ utilizes is controllable through the WithDegreeOfParallelism operator; the maximum number of workers utilized by the Parallel loops is controllable through the ParallelOptions class, an instance of which is supplied as a parameter to overloads of the looping constructs.
ASP.NET already takes advantage multiple processors on your server. Most developers will not need to explicitly add any parallelism into their ASP.NET Web applications. However, if your particular situation requires explicit parallelism, the new parallelism APIs in .NET 4 can be beneficial to you.