Anders Hattestad
Oct 12, 2011
  3881
(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.

Please login to comment.
Latest blogs
Opti ID overview

Opti ID allows you to log in once and switch between Optimizely products using Okta, Entra ID, or a local account. You can also manage all your use...

K Khan | Jul 26, 2024

Getting Started with Optimizely SaaS using Next.js Starter App - Extend a component - Part 3

This is the final part of our Optimizely SaaS CMS proof-of-concept (POC) blog series. In this post, we'll dive into extending a component within th...

Raghavendra Murthy | Jul 23, 2024 | Syndicated blog

Optimizely Graph – Faceting with Geta Categories

Overview As Optimizely Graph (and Content Cloud SaaS) makes its global debut, it is known that there are going to be some bugs and quirks. One of t...

Eric Markson | Jul 22, 2024 | Syndicated blog

Integration Bynder (DAM) with Optimizely

Bynder is a comprehensive digital asset management (DAM) platform that enables businesses to efficiently manage, store, organize, and share their...

Sanjay Kumar | Jul 22, 2024