Views: 3031
Number of votes: 4
Average rating:

A quick fix when saving to DSS takes a bit too much time

In a project I’m currently working on I need to update some DSS tables when a user is viewing a page. This is not a problem, but sometimes the save method takes a bit to much time.

Since the saving part of the data  not necessarily need to be done in that user thread, I have created a Lazy saver class that do the saving in a different thread.

All I needed to do is to implement my DDS classes with an interface ISaveMe and then save the item like this

Code Snippet
  1. public void SaveMe()
  2. {
  3.     Store.Save(this);
  4. }
  5. public void Save()
  6. {
  7.     LazyDSSSave.Current.AddToSave(this);
  8. }
  9. public static void Save(T item)
  10. {
  11.     LazyDSSSave.Current.AddToSave(item);
  12. }
  13. public static DynamicDataStore Store
  14. {
  15.     get
  16.     {
  17.         return DynamicDataStoreFactory.Instance.GetStore(typeof(T));
  18.     }
  19. }
Then the code bellow will start a new thread if needed an do the saving there.

It is important to remember that all exceptions in a thread that is not suppressed will make the app pool to restart. Therefore is very important to catch all errors.

Code Snippet
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Collections;
  6. using System.Threading;
  7.  
  8. namespace Itera.Data
  9. {
  10.     public interface ISaveMe
  11.     {
  12.         void SaveMe();
  13.     }
  14.     public class LazyDSSSave
  15.     {
  16.         protected Queue ActiveQueue = new Queue();
  17.         ThreadStart backgroundJob;
  18.         Thread thread;
  19.         #region Current
  20.         private static LazyDSSSave _current = new LazyDSSSave();
  21.         public static LazyDSSSave Current
  22.         {
  23.             get
  24.             {
  25.                 if (_current == null)
  26.                     _current = new LazyDSSSave();
  27.                 return _current;
  28.             }
  29.         }
  30.         #endregion
  31.  
  32.         public void AddToSave(object action)
  33.         {
  34.  
  35.             lock (ActiveQueue.SyncRoot)
  36.             {
  37.                 if (!ActiveQueue.Contains(action))
  38.                 {
  39.                     ActiveQueue.Enqueue(action);
  40.                 }
  41.             }
  42.             EnsureBackgroundJob();
  43.         }
  44.  
  45.         object lockObject = new object();
  46.         #region EnsureBackgroundJob
  47.         public void EnsureBackgroundJob()
  48.         {
  49.             lock (lockObject)
  50.             {
  51.                 if (thread == null || backgroundJob == null || !thread.IsAlive)
  52.                 {
  53.                     if (this.backgroundJob == null)
  54.                     {
  55.                         this.backgroundJob = new ThreadStart(this.Worker);
  56.                         
  57.                     }
  58.                     if (this.thread == null || !thread.IsAlive)
  59.                     {
  60.                         this.thread = new Thread(this.backgroundJob);
  61.                         this.thread.IsBackground = true;
  62.                         this.thread.Start();
  63.                     }
  64.                 }
  65.             }
  66.         }
  67.         #endregion
  68.       
  69.         #region Worker
  70.  
  71.         private void Worker()
  72.         {
  73.             try
  74.             {
  75.                 while (true)
  76.                 {
  77.                     object item = null;
  78.                     lock (ActiveQueue.SyncRoot)
  79.                     {
  80.                         if (ActiveQueue.Count == 0)
  81.                             return;
  82.                         item = ActiveQueue.Dequeue();
  83.                         
  84.                     }
  85.                     try
  86.                     {
  87.                         if (item is ISaveMe)
  88.                             (item as ISaveMe).SaveMe();
  89.                     }
  90.                     catch (Exception exception)
  91.                     {
  92.                        
  93.                         //log.Error("", exception);
  94.                     }
  95.                 }
  96.             }
  97.             catch (System.Exception error)
  98.             {
  99.                 //log.Error("", exception);
  100.             }
  101.             finally
  102.             {
  103.                 lock (lockObject)
  104.                 {
  105.                     if (backgroundJob != null)
  106.                         backgroundJob = null;
  107.                 }
  108.             }
  109.         }
  110.         #endregion
  111.     }
  112. }

 

This saves me sometimes as much as  70-100ms on each page request.

Oct 12, 2011

Magnus Rahl
(By Magnus Rahl, 10/12/2011 3:15:16 PM)

Any special reason you decided to build your own queueing/thread solution instead of using the ThreadPool?

Anders Hattestad
(By Anders Hattestad, 10/12/2011 3:21:22 PM)

Its based on the EPiServer.LazyIndexer code (at least some time ago)
I have used that way of handling this type of problems before, but I see your point :)
Its imo easier to add some statistics and logging here that can be viewed in a report later since you have one place to store these kind of data.

Please login to comment.