1

I'm trying to make my application thread safe. I hold my hands up and admit I'm new to threading so not sure what way to proceed.

To give a simplified version, my application contains a list.

  • Most of the application accesses this list and doesn't change it but may enumerate through it. All this happens on the UI thread.
  • Thread one will periodically look for items to be Added and Removed from the list.
  • Thread two will enumerate the list and update the items with extra information. This has to run at the same time as thread one as can take anything from seconds to hours.

The first question is does anyone have a recommend stragy for this.

Secondly I was trying to make seperate copies of the list that the main application will use, periodically getting a new copy when something is updated/added or removed, but this doesn't seem to be working.

I have my list and a copy......

public class MDGlobalObjects
{
    public List<T> mainList= new List<T>();
    public List<T> copyList
    {
        get
        {
            return new List<T>(mainList);
        }
    }
}

If I get copyList, modify it, save mainlist, restart my application, load mainlist and look again at copylist then the changes are present. I presume I've done something wrong as copylist seems to still refer to mainlist.

I'm not sure if it makes a difference but everything is accessed through a static instance of the class.

public static MDGlobalObjects CacheObjects = new MDGlobalObjects();
John Arlen
  • 6,539
  • 2
  • 33
  • 42
Oli
  • 2,996
  • 3
  • 28
  • 50
  • How are you saving and loading? – Oded May 01 '12 at 18:39
  • possible duplicate of http://stackoverflow.com/questions/1813557/c-sharp-list-concurrent-removing-and-adding – hatchet - done with SOverflow May 01 '12 at 18:43
  • .NET 4 has a [ConcurrentBag](http://msdn.microsoft.com/en-us/library/dd381779.aspx) that will help if you don't need an ordered list. – Nick Butler May 01 '12 at 18:45
  • You will still have to make sure your Thread 2 that modifies the objects in the list keeps them in a consistent state – Nick Butler May 01 '12 at 18:47
  • As always I suggest reading [Joe Albahari's ebook](http://www.albahari.com/threading/) – Nick Butler May 01 '12 at 18:49
  • @oded - I'm Simply saving to and loading from a file via a binaryformatter. – Oli May 01 '12 at 18:52
  • @nicholas Would I lock the object before enumerating it/editing the contents. If so am I better to create a copy, edit that, then update the main list based on the copy, or is there a better way. I say this as the lock could last a very long time. – Oli May 01 '12 at 18:55
  • The main thing is to keep the collection and the objects in it consistent - that is so they can be used by other threads without locks. One option for Thread 2 would indeed be to take a copy of an object, modify the copy at its leisure, then overwrite the reference in the collection as an atomic operation. You wouldn't need any locks for that if you use one of the concurrent collections - a `ConcurrentDictionary` would work if you want to identify elements. – Nick Butler May 01 '12 at 19:03
  • Thanks @NicholasButler - I like that idea. I presume I'd just check if the Object exists and if so overwrite. I can even do this as I go and not at the end. Would you be able to post some Pseudocode with the suggested collection to use and I'll mark it as an answer. – Oli May 01 '12 at 19:52

3 Answers3

1

actly copylist is just a shallow copy of the mainList. the list is new but the refrences of the objects contained in the list are still the same. to achieve what you are trying to you have to make a deep copy of the list something like this

public static IEnumerable<T> Clone<T>(this IEnumerable<T> collection) where T : ICloneable
{
    return collection.Select(item => (T)item.Clone());
}

and use it like

return mainList.Clone();
Parv Sharma
  • 12,581
  • 4
  • 48
  • 80
  • Thanks Parv, so are the objects contained within Thread Safe? I.E if I make the container thread safe do I still need to worry about two thread accessing the same object concurrently or will not be an issue if I'm always going via the list. – Oli May 01 '12 at 19:02
  • "if I get copyList, modify it, save mainlist", restart my application here do u mean that u want to save copyList.. your ques dosent makes much sense otherwise. – Parv Sharma May 01 '12 at 19:11
  • Hi Parv, No... I just used this as a test to confirm I wasn't getting a copy as expected. I'll only ever be saving and modifying Mainlist. Copylist was just a way of getting round the thread safe issue but I'm not even sure I need to do this anymore or if one of the concurrent collections will solve this problem. – Oli May 01 '12 at 19:39
  • i have added one more ans using thread safe collection in .Net 4.0 – Parv Sharma May 01 '12 at 19:51
1

looking at your ques again.. i would like to suggest an overall change of approach. you should use ConcurrentDictionary() as you are using .Net 4.0. in that you wont hav eto use locks as a concurrent collection always maintains a valid state.
so your code will look something like this.

Thread 1s code --- <br>
var object = download_the_object();
dic.TryAdd("SomeUniqueKeyOfTheObject",object);
//try add will return false so implement some sort of retry mechanism

Thread 2s code
foreach(var item in Dictionary)
{
 var object item.Value;
var extraInfo = downloadExtraInfoforObject(object);
//update object by using Update
dictionary.TryUpdate(object.uniqueKey,"somenewobjectWithExtraInfoAdded",object);
}
Parv Sharma
  • 12,581
  • 4
  • 48
  • 80
  • Hi Parv, Same question as I've just asked Nicholas above. Thread 2s code appears to enumerate through the dictionary so do I have to do something to make it thread safe? – Oli May 01 '12 at 22:46
  • no this code uses ConcurrentDictionary so it is already thread safe.. if you would have used normal collections then there wud hav been a need to locking mechanism and making it thread safe. rit nwo it already is – Parv Sharma May 02 '12 at 05:25
  • you just need to implement some sort of retry mecchanism because methods of concurrent collections like 'TryAdd' or AddOrUpdate or TryRemove return True OR False (boolean) so accordingly you should retry – Parv Sharma May 02 '12 at 05:26
1

This is the gist using a ConcurrentDictionary:


public class Element
{
    public string Key { get; set; }
    public string Property { get; set; }

    public Element CreateCopy()
    {
        return new Element
        {
            Key = this.Key,
            Property = this.Property,
        };
    }
}

var d = new ConcurrentDictionary<string, Element>();

// thread 1
// prune
foreach ( var kv in d )
{
    if ( kv.Value.Property == "ToBeRemoved" )
    {
        Element dummy = null;
        d.TryRemove( kv.Key, out dummy );
    }
}

// thread 1
// add
Element toBeAdded = new Element();
// set basic properties here
d.TryAdd( toBeAdded.Key, toBeAdded );

// thread 2
// populate element
Element unPopulated = null;
if ( d.TryGetValue( "ToBePopulated", out unPopulated ) )
{
    Element nowPopulated = unPopulated.CreateCopy();

    nowPopulated.Property = "Populated";

    // either
    d.TryUpdate( unPopulated.Key, nowPopulated, unPopulated );

    // or
    d.AddOrUpdate( unPopulated.Key, nowPopulated, ( key, value ) => nowPopulated );
}

// read threads
// enumerate
foreach ( Element element in d.Values )
{
    // do something with each element
}

// read threads
// try to get specific element
Element specific = null;
if ( d.TryGetValue( "SpecificKey", out specific ) )
{
    // do something with specific element
}

In thread 2, if you can set properties so that the whole object is consistent after each atomic write, then you can skip making a copy and just populate the properties with the object in place in the collection.

There are a few race conditions in this code, but they should be benign in that readers always have a consistent view of the collection.

Nick Butler
  • 24,045
  • 4
  • 49
  • 70
  • Thanks Nicholas - According to MSDN "ConcurrentDictionary is the one concurrent collection that does not support thread-safe enumeration." - Does that mean I need to do something special when enumerating with it? – Oli May 01 '12 at 22:45
  • That can't be true - it would make the collection useless. [MSDN: ConcurrentDictionary.GetEnumerator](http://msdn.microsoft.com/en-us/library/dd287131.aspx) says: "The enumerator returned from the dictionary is safe to use concurrently with reads and writes to the dictionary." – Nick Butler May 02 '12 at 08:21
  • I was somewhat confused by the statement. It's in the comments in the source code here http://msdn.microsoft.com/en-us/library/dd997369.aspx - Maybe a mistake? – Oli May 02 '12 at 16:40
  • Went a slightly different route in the end but marking this as answer as would also work. Actually went with your first comment of using concurrentbag. Seems to work like a charm so thanks for the direction. – Oli May 02 '12 at 16:42
  • As I said: it can't be true. I would believe the documentation and common sense over a comment by whoever wrote the example. – Nick Butler May 02 '12 at 16:42