Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Thread safe update of Cached Reference data

Say I have several List properties. Something Like this:

List<CustomerTypes> CustomerTypes {get; set;}
List<FormatTypes> FormatTypes {get; set;}
List<WidgetTypes> WidgetTypes {get; set}
List<PriceList> PriceList {get; set;}

Because these values update very rarely, I am caching them in my WCF Service at startup. I then have a service operation that can be called to refresh them.

The service operation will query them all from the database something like this:

// Get the data from the database.
var customerTypes = dbContext.GetCustomerTypes();
var formatTypes = dbContext.GetFormatTypes();
var widgetTypes = dbContext.GetWidgetTypes ();
var priceList = dbContext.GetPriceList ();

// Update the references
CustomerTypes = customerTypes;
FormatTypes = formatTypes;
WidgetTypes = widgetTypes;
PriceList = priceList;

This results in very little time that these are not all in sync. However, they are not fully thread safe. (A call could access a new CustomerType and an old PriceList.)

How can I make it so that while I am updating the references, any use of these lists has to wait until all references have been updated?

like image 311
Vaccano Avatar asked Feb 21 '14 19:02

Vaccano


1 Answers

First put all of those lists in to a single container class.

Class TypeLists
{
    List<CustomerTypes> CustomerTypes {get; set;}
    List<FormatTypes> FormatTypes {get; set;}
    List<WidgetTypes> WidgetTypes {get; set}
    List<PriceList> PriceList {get; set;}
}

Then replace the old property accesses with a function call.

private readonly object _typeListsLookupLock = new object();
private volatile TypeLists _typeLists;
private volatile DateTime _typeListAge;

public TypeLists GetTypeList()
{
    if(_typeLists == null || DateTime.UtcNow - _typeListAge > MaxCacheAge)
    {
        //The assignment of _typeLists is thread safe, this lock is only to 
        //prevent multiple concurrent database lookups. If you don't care that 
        //two threads could call GetNewTypeList() at the same time you can remove 
        //the lock and inner if check.
        lock(_typeListsLookupLock)
        {
            //Check to see if while we where waiting to enter the lock someone else 
            //updated the lists and making the call to the database unnecessary.
            if(_typeLists == null || DateTime.UtcNow - _typeListAge > MaxCacheAge)
            {
                _typeLists = GetNewTypeList();
                _typeListAge = DateTime.UtcNow;
            }
        }
    }
    return _typeLists;
}

private TypeLists GetNewTypeList()
{
    var container = new TypeLists()
    using(var dbContext = GetContext())
    {
        container.CustomerTypes = dbContext.GetCustomerTypes();
        container.FormatTypes = dbContext.GetFormatTypes();
        container.WidgetTypes = dbContext.GetFormatTypes();
        container.PriceList = dbContext.GetPriceList ();
    }
    return container;
}

The reason we change from a property to a function is you did

SomeFunction(myClass.TypeLists.PriceList, myClass.TypeLists.FormatTypes);

You could have TypeLists changed out from under you in a multi-threaded environment, however if you do

var typeLists = myClass.GetTypeLists();
SomeFunction(typeLists.PriceList, typeLists.FormatTypes);

that typeLists object is not mutated between threads so you do not need to worry about it's value changing out from under you, you could do var typeLists = myClass.TypeLists but making it a function makes it is more clear that you could potentially get different results between calls.

If you want to be fancy you can change GetTypeList() so it uses a MemoryCache to detect when it should expire the object and get a new one.

like image 198
Scott Chamberlain Avatar answered Nov 04 '22 09:11

Scott Chamberlain