Alex Rivera | Logout

Properly locking a List<T> in MultiThreaded Scenarios?

Asked 2009-09-01T15:09:36.850
37

Okay, I just can't get my head around multi-threading scenarios properly. Sorry for asking a similar question again, I'm just seeing many different "facts" around the internet.

public static class MyClass {
    private static List<string> _myList = new List<string>;
    private static bool _record;

    public static void StartRecording()
    {
        _myList.Clear();
        _record = true;
    }

    public static IEnumerable<string> StopRecording()
    {
        _record = false;
        // Return a Read-Only copy of the list data
        var result = new List<string>(_myList).AsReadOnly();
        _myList.Clear();
        return result;
    }

    public static void DoSomething()
    {
        if(_record) _myList.Add("Test");
        // More, but unrelated actions
    }
}

The idea is that if Recording is activated, calls to DoSomething() get recorded in an internal List, and returned when StopRecording() is called.

My specification is this:

  • StartRecording is not considered Thread-Safe. The user should call this while no other Thread is calling DoSomething(). But if it somehow could be, that would be great.
  • StopRecording is also not officially thread-safe. Again, it would be great if it could be, but that is not a requirement.
  • DoSomething has to be thread-safe

The usual way seems to be:

    public static void DoSomething()
    {
        object _lock = new object();
        lock(_lock){
            if(_record) _myList.Add("Test");
        }
        // More, but unrelated actions
    }

Alternatively, declaring a static variable:

    private static object _lock;

    public static void DoSomething()
    {
        lock(_lock){
            if(_record) _myList.Add("Test");
        }
        // More, but unrelated actions
    }

However, c# .net multithreading

Edit
Report

1 Answer

8

There are a few ways to lock the list. You can lock on _myList directly providing _myList is never changed to reference a new list.

lock (_myList)
{
    // do something with the list...
}

You can create a locking object specifically for this purpose.

private static object _syncLock = new object();
lock (_syncLock)
{
    // do something with the list...
}

If the static collection implements the System.Collections.ICollection interface (List(T) does), you can also synchronize using the SyncRoot property.

lock (((ICollection)_myList).SyncRoot)
{
    // do something with the list...
}

The main point to understand is that you want one and only one object to use as your locking sentinal, which is why creating the locking sentinal inside the DoSomething() function won't work. As Jon said, each thread that calls DoSomething() will get its own object, so the lock on that object will succeed every time and grant immediate access to the list. By making the locking object static (via the list itself, a dedicated locking object, or the ICollection.SyncRoot property), it becomes shared across all threads and can effectively serialize access to your list.

answered 2009-09-01T17:40:25.873

Your Answer