views:

69

answers:

2

I have a class with several data members:

public class Foo
{
    List<int> intLst;
    int Whatever;
    HashTable<string> hshTable;
    ...
}

I then have a function within Foo that takes two Foos and merges them into a new Foo. I would like to make the Foo class thread safe too. Is the below code the best way to do this?

public Foo(Foo f)
{
    lock(f)
    {
        foreach (int i in f.intLst)
        {
            this.intLst.Add(i)
        }
        //More complicated
    }
}

public Foo Merge(Foo f1, Foo f2)
{
    Foo fReturn = new Foo(f1);//Using the constructor above

    lock(f2)
    {
        foreach (int i in f2.intLst)
        {
            fReturn.intLst.Add(i);
        }

        //More complicated merge code
    }

    return fReturn;
} 

Is it that simple? Should I use Monitors instead? I know lock implements the monitor class, but is there perhaps still a reason. Also do I need to worry about handling deadlock in my above sample.

I feel like a I need to lock() both f1 and f2 because what if f2 changes during the copy constructor on f1?

A: 

lock() only works on other lock() blocks. So lock(f) will block other code that is working with f as long as that code also has lock(f), so lock(f) has to be on all usages of (f).

You don't need to worry about locking the target Foo since it's still being constructed--nothing else can have a reference to it yet.

Sam
+2  A: 

Yes, it is that simple.

Your code is good. You don't need to lock f2 until you access it, and you don't need to lock f1 after it has been copied. So there really is no need to lock both at the same time.

In your example the items themselves are simple integers which are immutable so there is no need to lock them, but if you had mutable items you may need to lock the individual items.

You don't need to worry about deadlock with just this code since you only hold one lock at a time. Of course if some other code calls it while holding a lock you might need to worry about the possiblity of deadlock.

Ray Burns
In my real application, the items are mutable. Once I call the copy constructor in Merge(), I believe f2 could change before it is locked? Is this correct. I want to ensure that once the Merge function called, both f1 and f2 cannot be changed. I might just need some reassurance.
Ames
The scenario you need to consider is this: 1. Your code copies f1 then releases the lock. 2. Another thread makes a single change to both f1 and f2. 3. Your code copies f2 then releases the lock. In this scenario, your code will have a copy of f1 from before the other thread's changes and a copy of f2 from afterward. In your particular application, would this ever be a bad thing? If the answer is yes, you need additional locking. If the answer is no, you don't. Generally the answer will be yes if both are part of a single structure, in which case you should probably lock the structure.
Ray Burns
Thanks for the clarification.
Ames