.NET GC Доступ к синхронизированному объекту из финализатора - PullRequest
2 голосов
/ 07 марта 2011

Я недавно прочитал эту статью Безопасная синхронизация потоков , так как мне было интересно узнать о безопасности потоков при вызовах из финализатора. Я написал следующий код для проверки доступа к статической поточно-безопасной коллекции из финализатора.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace GCThreadTest
{
    class Program
    {
        static class FinaliserCollection
        {
            private static Queue<int> s_ItemQueue = new Queue<int>();
            private static System.Object s_Lock = new System.Object();

            public static void AddItem(int itemValue)
            {
                lock(s_Lock)
                {
                    s_ItemQueue.Enqueue(itemValue);
                }
            }

            public static bool TryGetItem(out int item)
            {
                lock(s_Lock)
                {
                    if (s_ItemQueue.Count <= 0)
                    {
                        item = -1;
                        return false;
                    }

                    item = s_ItemQueue.Dequeue();
                    return true;
                }
            }
        }

        class FinaliserObject
        {
            private int m_ItemValue;

            public FinaliserObject(int itemValue)
            {
                m_ItemValue = itemValue;
            }

            ~FinaliserObject()
            {
                FinaliserCollection.AddItem(m_ItemValue);
            }
        }

        static void Main(string[] args)
        {
            int itemValueIn = 0;
            int itemValueOut = 0;

            while (itemValueOut < 10000)
            {
                System.Threading.ThreadPool.QueueUserWorkItem
                    (delegate(object value)
                    {
                        new FinaliserObject((int)value);

                        System.Threading.Thread.Sleep(5);

                    }, itemValueIn);

                itemValueIn = itemValueIn + 1;

                // This seems to stop finaliser from
                // being called?
                // System.Threading.Thread.Sleep(5);

                int tempItemValueOut = -1;
                if (FinaliserCollection.TryGetItem(out tempItemValueOut))
                    itemValueOut = tempItemValueOut;
            }

            System.Console.WriteLine("Finished after {0} items created", itemValueOut);
            System.Console.ReadLine();
        }
    }
}

Без вызова «Sleep» в цикле while этот код работает нормально, но действительно ли он безопасен от блокировки? Возможно ли когда-либо сделать вызов финализатора, когда элемент пула потоков в очереди получает доступ к статической коллекции? Почему добавление 'Sleep' к основным потокам в то время как цикл появляется, чтобы остановить вызов всех финализаторов?

1 Ответ

3 голосов
/ 07 марта 2011

Ничего себе.Что за ... Это самый странный кусок кода, который я когда-либо видел.@. @

Прежде всего, на какой вызов финализатора вы ссылаетесь?Единственный финализатор, который я вижу, это финализатор для FinaliserObject, который будет вызываться 10000 раз и может вызываться независимо от того, что происходит в статической коллекции.То есть да, эти объекты могут быть уничтожены, в то время как другие объекты удаляются из коллекции.Это не проблема.

Сама статическая коллекция не будет очищена, пока само приложение не выйдет.

Имейте в виду, что абсолютно никаких гарантий приили если эти финализаторы будут вызваны до выхода самого приложения.Ваша статическая коллекция может быть полностью пустой при выходе.

Хуже того, вы присваиваете itemValueOut любому последнему значению, которое вы извлекаете из очереди ... которое НЕ является числом созданных элементов,как вы подразумеваете в своем WriteLine ().Поскольку эти деструкторы вызываются в любом возможном порядке, вы можете теоретически добавить в очередь 10 000, 9 999, 9 998, ... 2, 1 в этом порядке.

Что является еще одной проблемой, поскольку вы удаляете из очереди 10000 раз, но в последнем цикле вполне возможно, что не будет объекта для удаления из очереди, и в этом случае вы гарантированнополучить -1 за количество возвращенных элементов (даже если остальные 9 999 элементов работали успешно).

Чтобы ответить на ваш вопрос, этот код не может зайти в тупик.Если бы AddItem() вызвал TryGetItem(), то возникла бы тупиковая ситуация, но эти блокировки в значительной степени гарантировали, что они не будут допускать друг друга в статической коллекции при добавлении или удалении элементов.может выйти из вашего приложения без добавления всех FinaliserObject в очередь.Это означает, что один из финализаторов может выстрелить и попытаться добавить к FinaliserCollection, но FinaliserCollection уже удален.То, что вы делаете в финализаторе: ужасно .

Но да, вызов финализатора может произойти, пока вы звоните FinaliserCollection.TryGetItem().Финализатор заблокируется и будет ждать, пока TryGetItem() не выйдет из lock(), после чего он добавит еще один элемент.Это не проблема.

Что касается команды sleep(), вы, вероятно, просто выбрасываете время сбора мусора.Помните, что ваши объекты не будут собраны / доработаны, пока GC не решит, что ему нужны ресурсы.

Извините за столь настойчивый ... Я знаю, вы просто пытаетесь протестировать концепцию, но я действительно неЯ не понимаю, почему вы хотите делать то, что пытаетесь сделать в финализаторе.Если здесь есть действительно легитимная цель, то ее выполнение в финализаторе - это , а не правильный ответ.

Редактировать Из того, что я читаю и что говорит Саша, нет, у тебя не будет тупика.Поток финализатора может быть заблокирован в ожидании блокировки, но сборщик мусора не будет ждать финализатора и, таким образом, отсоединит потоки, позволяя снять блокировки.

В любом случае это очень сильныйаргумент, почему вы не должны делать подобные вызовы в финализаторе ... финализатор всего для освобождения неуправляемых ресурсов.Все остальное играет в рулетку.

...