1

I have 2 main threads to my program:

  1. Thread checkForNewItemsThread Adds new objects to a volatile List<Object>
  2. Thread workOnListThread Works on the List<Object> and creates another new Thread doWorkThread for each item in list that needs work.

I am running into an ArgumentNull Exception during my workOnListThread because (what I think is happening) either:

  1. My checkForNewItemsThread is adding to the list while workOnListThread is iterating through the list.
  2. My doWorkThreadis removing the item from the list once it is finished working on that object.

I believe the right hing to do here would be to put a lock on the List<Object> while workOnListThread is working on the list, however, I'm having a hard time determining the right place to place the lock on the List<Object>, I think this should be correct, however could use some guidance.

volatile List<Object> List1 = new List<Object>();
private static readonly object _lock = new object();

Thread checkForNewItemsThread;
Thread workOnListThread;
Thread doWorkThread;

public void OnTimerCheckNewItems(object sender, ElapsedEventArgs args)
    {
        List<Object> List1Temp = new List<Object>();
        List1Temp = getList();
        addNewItems(List1Temp);
    }
private void addNewItems(List<Object> list)
    {
        foreach (Object obj in list)
        {
            if (!List1.Any(o => o.ID == obj.ID)) //check if object already exists
            {
                List1.Add(obj);
            }
        }
    }
private void workOnList()
    {
        while (true) //loop forever
        {
            while (List1.Count != 0) //while items exist
            {
                lock (_lock) //lock while iterating through list
                {
                    try
                    {
                        for (int i = 0; i < List1.Count; i++)
                        {
                            if (List1[i].Status == Status.ready && List1[i] != null)
                            {
                                doWorkThread = new Thread(() => doWork(List1[i]));
                                doWorkThread.Start();
                                Thread.Sleep(3000); // wait for process to start
                            }
                        }
                    }
                    catch (Exception e)
                    {
                        Console.WriteLine(DateTime.Now.ToString(), " : ", e);
                        Log(e.ToString());
                    }
                }
            }
        }
    }
private void doWork(Object obj)
    {
        lock (_lock) //lock during update to status
        {
            int i = List.IndexOf(obj);
            List1[i].Status = Status.pending;
        }

        //work done here

        List1.Remove(alert);
    }
Dillon Tucker
  • 55
  • 1
  • 8
  • Take a look at this excellent resource on multithreading in C#: http://www.albahari.com/threading/ – jscarle May 14 '20 at 18:38

1 Answers1

2

The List<T> class is not thread-safe, so if you want to access it from multiple threads concurrently you must protect every access to it using a lock. The right place to place the lock is on every place that you are reading/writing a property of the list, or calling any of its methods. Otherwise, the only guarantee that you get from the manufacturers of the class is that there is no guarantee. Officially the behavior of the class becomes "undefined", which is quite terrifying if you are trying to make a program that should be reliable regarding the correctness of its results.

Since locking creates contention, you must hold a lock as briefly as possible. Don't do anything inside the lock that is not related to the List itself. For example if you need to enumerate the elements of the list and do something with each element, it is probably preferable to acquire the lock, take a local snapshot of the list, release the lock, and finally enumerate the local snapshot.

An alternative to synchronizing manually a List is to use one of the available concurrent collections, like the ConcurrentQueue or the ConcurrentDictionary. These classes offer specialized APIs that perform atomic operations very efficiently, but are generally awkward to use, and are not as flexible as the manual synchronization.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104