0

When I tried to learn C# List<> underlying principles, I get the following information from here:

List uses the default constructor to create an empty list. As elements are added to the list, the capacity of the list expands to accommodate 4 elements. If a fifth element is added, the list is resized to contain 8 elements. Include 16 if 8 isn't enough. In short, each time the capacity of the list is reset to 2 times the original size.

That is easy to understand, but if I create Thread and add them to List<Thread>, I notice that it will still run the original Thread. Here is the code:

        static void Main(string[] args)
    {
        List<Thread> LT = new List<Thread>();
        for (int i = 0; i < 4; i++)
        {
            int time = i;
            LT.Add(new Thread(() => { Thread.Sleep(1000 * time); Console.WriteLine(time); }));
            LT[i].Start();
        }
        for (int i = 4; i < 8; i++)
        {
            int time = i;
            LT.Add(new Thread(() => { Thread.Sleep(1000 * time); Console.WriteLine(time); }));
            LT[i].Start();
        }


        Console.ReadLine();
    }

I opened the source code of List, and I found Array.Copy(_items, 0, array, 0, _size);, which means it will deep copy the array. In that case, I tried to use GC.Collect(); to force the program delete the original array. However, threads created before the expansion mechanism occurred would still run while not changing anything, here is the output: 0 1 2 3 4 5 6 7

I wondering how C# implement this function.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
Han Han
  • 328
  • 12
  • 1
    *"I notice that it will still run the original Thread"* -- How did you notice this? What is the output that you expected to get, instead of the actual `0 1 2 3 4 5 6 7`? – Theodor Zoulias Jul 30 '23 at 18:20
  • @TheodorZoulias I added `Thread.Sleep(1000 * time);`, which can show that they still run the original threads, because if threads are changed, the time to sleep should be reset. – Han Han Jul 30 '23 at 18:40
  • 5
    "I found Array.Copy [...] which means it will deep copy the array" -- where are you people getting these ideas from? – Blindy Jul 30 '23 at 19:07
  • 1
    So you expected that after the internal expansion of the `List`, your threads would be replaced with some other threads? What would be these other threads? Who would instantiate them, and how? The [`Thread`](https://learn.microsoft.com/en-us/dotnet/api/system.threading.thread) class doesn't have a parameter-less constructor. – Theodor Zoulias Jul 30 '23 at 19:29
  • That would be horrible, quite frankly. – Fildor Jul 30 '23 at 19:30
  • 1
    "That is easy to understand" - it doesn't seem to be. Once you start a thread then that thread will run to completion no matter if you add it to a list or not. Adding to a list does not change the behaviour of the thread. – Enigmativity Jul 31 '23 at 00:46

1 Answers1

2

Collection (lists, arrays) holding objects of reference types hold in reality only references to said objects and not the objects themselves.

All that Array.Copy is doing with respect to the source and target arrays being arrays of reference types is copying references. It doesn't delete/destroy objects (that includes Thread objects as well), and neither does it create new ones.

In that sense, it is no different than doing:

var threadVar1 = new Thread(() => { Thread.Sleep(1000 * time); Console.WriteLine(time); });
var threadVar2 = threadVar1;

Array.Copy is doing pretty much the same thing, with threadVar1 and threadVar2 being the respective elements in the source and target arrays (but using CPU instructions that allow copying multiple elements at once instead of copying every array element individually). Copying the reference of a Thread object from threadVar1 to threadVar2 doesn't change anything about the Thread object, and neither does copying some Thread object references from one array to another.

Even if you were to set threadVar1 to null after assigning its value to threadVar2, it wouldn't change a thing because threadVar2 still holds a reference to the Thread object. No matter how often you run the garbage collector, as long as something in your program's process holds a reference to the Thread object (like threadVar2), the Thread object won't be collected/destroyed by the garbage collector.

Equivalently, even if the original source array becomes subject to garbage collection once the List<Thread> instance is finished with expanding its array capacity, there are now references to the very same Thread objects being hold by the target array (the one that becomes the new backing array for the list) and thus the Thread objects won't be collected/destroyed by the garbage collector.

But, with respect to Thread objects, even in the case of the list and its active backing array itself becoming subject to garbage collection, the Thread objects themselves might not be garbage collected until the actual threads represented by the Thread objects are finished running or have been killed. The CLR itself keeps track of all running threads and therefore itself might hold references to Thread objects of active threads preventing them from being garbage-collected.


Reminder: This answer is with respect to the source and target arrays for Array.Copy being arrays of reference types. Array.Copy functionality is not limited to copying between arrays of reference types, but that's another story unrelated to the question.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
NoNoNuh-uh
  • 44
  • 2