public IEnumerable EnumerateGCRoots()

in src/Microsoft.Diagnostics.Runtime/src/Common/GCRoot.cs [83:171]


        public IEnumerable<GCRootPath> EnumerateGCRoots(ulong target, bool returnOnlyFullyUniquePaths, int maxDegreeOfParallelism, IEnumerable<IClrRoot> roots, CancellationToken cancellationToken = default)
        {
            if (roots is null)
                throw new ArgumentNullException(nameof(roots));

            bool parallel = Heap.Runtime.IsThreadSafe && maxDegreeOfParallelism > 1;

            Dictionary<ulong, LinkedListNode<ClrObject>> knownEndPoints = new Dictionary<ulong, LinkedListNode<ClrObject>>()
            {
                { target, new LinkedListNode<ClrObject>(Heap.GetObject(target)) }
            };

            if (!parallel)
            {
                int count = 0;

                ObjectSet processedObjects = new ObjectSet(Heap);
                foreach (IClrRoot root in roots)
                {
                    LinkedList<ClrObject>? path = PathsTo(processedObjects, knownEndPoints, root.Object, target, returnOnlyFullyUniquePaths, cancellationToken).FirstOrDefault();
                    if (path != null)
                        yield return new GCRootPath(root, path.ToImmutableArray());

                    if (count != processedObjects.Count)
                    {
                        count = processedObjects.Count;
                        ProgressUpdated?.Invoke(this, count);
                    }
                }
            }
            else
            {
                ParallelObjectSet processedObjects = new ParallelObjectSet(Heap);
                ConcurrentQueue<GCRootPath> results = new ConcurrentQueue<GCRootPath>();
                using BlockingCollection<IClrRoot?> queue = new BlockingCollection<IClrRoot?>();

                Thread[] threads = new Thread[Math.Min(maxDegreeOfParallelism, Environment.ProcessorCount)];
                for (int i = 0; i < threads.Length; i++)
                {
                    threads[i] = new Thread(() => WorkerThread(queue, results, processedObjects, knownEndPoints, target, all: true, returnOnlyFullyUniquePaths, cancellationToken)) { Name = "GCRoot Worker Thread" };
                    threads[i].Start();
                }

#pragma warning disable CA2016 // Forward the 'CancellationToken' parameter to methods that take one (BlockingCollection is unbounded so Add will not block)
                foreach (IClrRoot root in roots)
                    queue.Add(root);

                // Add one sentinel value for every thread
                for (int i = 0; i < threads.Length; i++)
                    queue.Add(null);
#pragma warning restore CA2016 // Forward the 'CancellationToken' parameter to methods that take one

                queue.CompleteAdding();

                int count = 0;

                // Worker threads end when they have run out of roots to process.  While we are waiting for them to exit, yield return
                // any results they've found.  We'll use a 100 msec timeout because processing roots is slooooow and finding a root is
                // rare.  There's no reason to check these results super quickly and starve worker threads.
                for (int i = 0; i < threads.Length; i++)
                {
                    while (!threads[i].Join(100))
                    {
                        if (cancellationToken.IsCancellationRequested)
                            yield break;

                        while (results.TryDequeue(out GCRootPath result))
                            yield return result;
                    }

                    if (count != processedObjects.Count)
                    {
                        count = processedObjects.Count;
                        ProgressUpdated?.Invoke(this, count);
                    }
                }

                // We could have raced to put an object in the results queue while joining the last thread, so we need to drain the
                // results queue one last time.
                while (results.TryDequeue(out GCRootPath result))
                    yield return result;

                if (count != processedObjects.Count)
                {
                    count = processedObjects.Count;
                    ProgressUpdated?.Invoke(this, count);
                }
            }
        }