Anders Hattestad
Oct 12, 2011
visibility 4500
star star star star star star
(4 votes)

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

Comments

Magnus Rahl
Magnus Rahl Oct 12, 2011 01:15 PM

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

Anders Hattestad
Anders Hattestad Oct 12, 2011 01:21 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.

error Please login to comment.
Latest blogs
Composition Over Inheritance: Why Your Content Contract Is Not a Base Class

Inside the structural conformance pattern in SaaS CMS — from server-side inheritance to pipeline governance. A contract in Optimizely SaaS CMS is...

Vipin Banka | Sep 19, 2026

Optimizely Opti ID Integration: Why the API Key Was Missing from the DXP Management Portal

While working on an Optimizely CMS 12 Opti ID integration recently, we ran into an issue that initially appeared to be related to SSO authenticatio...

Madhu | Sep 18, 2026 |

Extending a Contract: What You Can Override

Contracts share fields across content types — but not every field setting can be overridden. Push the same contract onto several components, and CM...

Chirag Khanna | Sep 18, 2026 |

Building a Blazor Server UI Inside the Optimizely CMS 13 Admin Shell

What I learned shipping a Blazor Server UI inside Optimizely CMS 13 — Razor components in a NuGet package, the Blazor hub next to MapContent(),...

Stanisław Szołkowski | Sep 17, 2026 |