Anders Hattestad
Oct 12, 2011
  3337
(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
Preview multiple Visitor Groups directly while browsing your Optimizely site

Visitor groups are great - it's an easy way to add personalization towards market segments to your site. But it does come with it's own set of...

Allan Thraen | Sep 26, 2022 | Syndicated blog

The Report Center is finally back in Optimizely CMS 12

With Episerver.CMS.UI 12.12.0 the Report Center is finally re-introduced in the core product.

Tomas Hensrud Gulla | Sep 26, 2022 | Syndicated blog

Dynamic Route in ASP.NET Core When MapDynamicControllerRoute Does Not Work

Background Creating one of the add-on for Optimizely I had to deal with challenge to register dynamically route for the API controller. Dynamic rou...

valdis | Sep 25, 2022 | Syndicated blog

404 Error on Static Assets Within an Optimizely plugin

Background With the move to CMS 12 and .NET 5/6, developers are now able to build Plugins and Extensions using Razor Class Libraries (RCL).  These...

Mark Stott | Sep 23, 2022

How to bypass the content creation view in Optimizely

Something that has come up a couple of times in the last few year is feedback from content editors about the editing view that comes up when creati...

Ynze | Sep 23, 2022 | Syndicated blog

Welcome to Optimizely World's New Tech Video Portal

Optimizely, leader in the digital experience realm, has become a wealth of world class SaaS products including Web Experimentation, Full Stack, B2B...

The Developer Marketing Team of Optimizely | Sep 22, 2022