Alik Levin's

Clarity, Technology, and Solving Problems | PracticeThis.com

Improve ASP.NET Performance With Multithreading Using Thread Or ThreadPool Objects

Improve ASP.NET Performance With Multithreading Using Thread Or ThreadPool Objects

  • Comments 8

Your ASP.NET application performs slower than expected? How many times do you access your database for each request? Did you use SQL Profiler to find out? Did you know that chatty database access is one of the most common performance sins (this is my own observation)?

Want to improve? - Reduce the number of database queries.

Not an option? - You may want using multithreading. Proceed with caution.

Real world scenario

You need to run 3 independent heavy database queries – Q1, Q2, Q3. You need to use the result from the three – Result = Q1+Q2+Q3. You never know the order in which each query completes. It can be: Q1Q2Q3, Q1Q3Q2, Q2Q3Q1, Q2Q1Q3, Q3Q2Q1, Q3Q1Q2. Missed any combination? Run one after another would create significant latency. Running each on its own thread would create latency of Max(Q1,Q2,Q3) which is less that sum of the three.

How to run each query on its own thread and wait for the completion for each one?

Using Thread object

The following code spawns three threads and waits for all to join, and then use the results from each.

   1: Thread t1 = new Thread(DoWork1);
   2: t1.Start();
   3:  
   4: Thread t2 = new Thread(DoWork2);
   5: t2.Start();
   6:  
   7: Thread t3 = new Thread(DoWork3);
   8: t3.Start();
   9:  
  10: t1.Join(1000);
  11: t2.Join(1000);
  12: t3.Join(1000);
  13:  
  14:  
  15: Label1.Text = (i1 + i2 + i3).ToString();

See any issues using it in ASP.NET? One caveat though, creating threads manually like this may cause performance hit as it involves context switching – CPU consuming operation. Consider using ThreadPool object.

Using ThreadPool object (preferred, but are you using COM?)

ThreadPool is preferred as it already has live threads allocated for you. No need to ask for a favor from CPU in the moment of truth. Here is the code:

   1: public partial class _Default : System.Web.UI.Page
   2: {
   3:     int i1 = 0;
   4:     int i2 = 0;
   5:     int i3 = 0;
   6:  
   7:     WaitHandle[] waitHandles = new WaitHandle[]
   8:         { 
   9:             new ManualResetEvent(false),
  10:             new ManualResetEvent(false),
  11:             new ManualResetEvent(false) 
  12:         };
  13:  
  14:  
  15:     protected void Page_Load(object sender, EventArgs e)
  16:     {
  17:  
  18:     }
  19:  
  20:     protected void Button1_Click(object sender, EventArgs e)
  21:     {
  22:         Stopwatch sw = new Stopwatch();
  23:  
  24:         sw.Start();
  25:  
  26:         WaitCallback method1 = new WaitCallback(DoWork1);
  27:         bool isQueued1 = ThreadPool.QueueUserWorkItem(method1, waitHandles[0]);
  28:  
  29:         WaitCallback method2 = new WaitCallback(DoWork2);
  30:         bool isQueued2 = ThreadPool.QueueUserWorkItem(method2, waitHandles[1]);
  31:  
  32:         WaitCallback method3 = new WaitCallback(DoWork3);
  33:         bool isQueued3 = ThreadPool.QueueUserWorkItem(method3, waitHandles[2]);
  34:  
  35:         if (WaitHandle.WaitAll(waitHandles, 5000, false))
  36:             Label1.Text = (i1 + i2 + i3).ToString();
  37:         else
  38:             Label1.Text = "Problem";
  39:  
  40:     }
  41:  
  42:      void DoWork1(object state)
  43:     {
  44:         int.TryParse(TextBox1.Text, out i1);
  45:  
  46:         //HEAVY QUERY GOES HERE. Sleep is for the demo only! Remove it!
  47:         Thread.Sleep(i1);
  48:         ManualResetEvent mre = (ManualResetEvent)state;
  49:         mre.Set();
  50:  
  51:     }
  52:  
  53:      void DoWork2(object state)
  54:     {
  55:         int.TryParse(TextBox2.Text, out  i2);
  56:  
  57:         //HEAVY QUERY GOES HERE. Sleep is for the demo only! Remove it!
  58:         Thread.Sleep(i2);
  59:         ManualResetEvent mre = (ManualResetEvent)state;
  60:         mre.Set();
  61:     }
  62:  
  63:      void DoWork3(object state)
  64:     {
  65:         int.TryParse(TextBox3.Text, out  i3);
  66:  
  67:         //HEAVY QUERY GOES HERE. Sleep is for the demo only! Remove it!
  68:         Thread.Sleep(i3);
  69:         ManualResetEvent mre = (ManualResetEvent)state;
  70:         mre.Set();
  71:     }
  72:  
  73: }

Caveat

If your function calls on COM object – avoid using ThreadPool, its Apartment model is not compatible with COM and it cannot be changed. More info - Pitfalls With .Net Multithreading And COM Objects – Threads Must Have Compatible Apartment Models (MTA vs. STA).

Another point to call out is that performance improvements based on multithreading is subject to amount of available CPU’s. Test your solution first!

This post was written with help from Lior, MCS Israel Architect.

Download sample project with the code from my SkyDrive:

Enjoy.

  • Two things: 1) Using the ThreadPool like this in ASP.NET is dangerous because requests are processed from thread pool threads and thus you could saturate the thread pool with so much work that ASP.NET can no longer process requests from clients, 2) Async pages is designed in ASP.NET to help achieve this in a more scalable way. Line 35 is blocking until all 3 tasks are done, thus wasting a thread in the thread pool that could be serving other requests. Async pages puts this thread back into the thread pool to serve other requests until all 3 tasks are complete.

  • Brock,

    Great points! I was writing this post with my fingers crossed (can you write like anyway?....LOL). I had mixed feelings just because of the points you call out...

    without delving into the details I needed to provide the solution to the application written in netfx 1.1

    Refer  to "Asynchronous Pages in ASP.NET 1.x"

    at

    http://msdn.microsoft.com/en-us/magazine/cc163725.aspx

    I needed something simpler than what they describe there.

    Narrowing my scenario... the customer needed to provided parallel work for the web site that is not under heavy load but needed faster response time. So having that in mind i thought the solution is suitable

    Is it?

  • I've had a similar requirements (not exactly the same as you state, but close enough) in the past and my approach was to use a custom thread pool to address the issue #1 I raised above. The implementation I used was from Mike Woodring at bearcanyon.com and it's still in production. Another reason I wanted to manually use a custom thread pool was because *many* of the built-in async APIs from the CLR use the TP internally which doesn't help the saturation problem.

    As I always like to say: Programming is hard. :)

  • Brock,

    Thanks for pointing to the custom threadpool implementation.

    I only hope that 1.1 projects will soon die (I think mainstream support ends this year for net fx 1.1), so the need for custom threadpool will be even lesser. Nevertheless I value a lot you pointed out this one to me – THANKS!

    I am reading now the book by John Robbins "Debugging .Net 2.0 applications".

    He warns to not do multithreading. I am so happy to hear that. My mantra - gimme one reason to use multithreading in your app and will give another million reason to not to. It seems to me that most (if not 100%) of the line of business projects may safely live w/out multithreading on server side.

    I’ll tell you the real reason of this post – I was asked to provide a better synchronization than infinite loop with sleep in it…….

    PS - i really appreciate your comments!

  • So, the conclusion is that we should always use async pattern as described in

    http://msdn.microsoft.com/en-us/magazine/cc163725.aspx

    rather than threadpool objects or multithreading ?

    Madhur

  • "Always" us very broad.

    Mark Twain would say: "All generalization is wrong, including this one".

    My take - seek the way to avoid using async and threading at all costs. Use it as the last resort.

    WHat's your scenario?

  • I have a webpart based ASP.NET portal in which webpart usually contains datagrid or graphs. All the displayed webparts are connected to one hidden datawebpart which supplies data to them trough webpart connections (dataset wrapped by interface).

    On couple of pages, with lots of webparts, the performance is going down. So I was planning to nail down this problem by async. Since webpart connections would be executed synchronously when they could be done in async .

  • oh, unfortunately i am not in the know wrt web parts development.

    Sorry about that.

Page 1 of 1 (8 items)