Нужно ли выполнять отписку от события, содержащемся в объекте, который будет собран GC?

Доброго времени суток, появился небольшой вопрос.
Имеется класс, например некий Task. Он содержит в себе событие TaskComplete, на которое кто-нибудь подписался. И есть некий класс Ship, содержащий свойство CurrentTask типа Task (ну или поле, не суть важно). Предположим, задача выполнена и CurrentTask присваивается другой экземпляр Task или null. Логично, что старый экземпляр будет через некоторое время собран GC.
Вроде бы очевидно, что собрано будет все и никаких проблем от того, что на то событие был кто-то подписан не будет (в отличие от обратной операции, когда удаляется подписчик, естественно). Но на всякий случай уточню - правильно ли я понимаю, что выполнять отписку от события, которое скоро будет собрано GC, нет смысла и это не приведет к memory leak?
  • Вопрос задан
  • 584 просмотра
Решения вопроса 2
@rare
Я попытался смоделировать вашу ситуацию и получилось, что не отписка не приводит к утечке памяти. Я использовал финализатор, чтобы отследить сборку мусора. В конце теста примерно 14% объектов не уничтожено, но вызов GC.Collect() приводит к их уничтожению. Я это интерпретирую так, что GC распознает эти объекты как мусор, просто еще не добрался до них. Это связано и с использованием кастомного финализатора: для уничтожения таких объектов сборщику мусора нужно больше времени. Подробнее про события https://habrahabr.ru/post/89529/
class Program
{
    private static bool Unsubscribe = false;
    static void Main(string[] args)
    {
        var ship = new Ship();
        Enumerable.Range(0, 100000).ToList().ForEach(i =>
        {
            Console.Write($"\r{i + 1}\t");
            ship.MyTask = new MyTask(i);
            ship.MyTask.TaskCompleted += OnTaskCompleted;
            ship.MyTask.DoAndRaise();
        });

        ship.MyTask = null;

        Console.WriteLine();
        Console.WriteLine("Raised: " + MyTask.EventsRaised);
        Console.WriteLine("Collected: " + MyTask.Collected);
        GC.Collect();
        GC.WaitForPendingFinalizers();
        Console.WriteLine("Collected: " + MyTask.Collected);
    }

    static void OnTaskCompleted(object semder, int taskId)
    {
        if (Unsubscribe)
        {
            var mt = semder as MyTask;
            mt.TaskCompleted -= OnTaskCompleted;
        }
    }
}

public class MyTask
{
    public static int Collected, EventsRaised;
    public MyTask(int id)
    {
        TaskId = id;
    }

    public int TaskId { get; }

    public event EventHandler<int> TaskCompleted;

    public void DoAndRaise()
    {
        TaskCompleted?.Invoke(this, TaskId);
        Interlocked.Increment(ref EventsRaised);
    }

    ~MyTask()
    {
        Interlocked.Increment(ref Collected);
    }
}

public class Ship
{
    public MyTask MyTask { get; set; }
}
Ответ написан
Комментировать
Is it necessary to explicitly remove event handler...
Отвечает Jon Skeet.

.. The tricky case is when the publisher is long-lived but the subscribers don't want to be - in that case you need to unsubscribe the handlers..

Если источник события скоро будет собран, на него нет других ссылок, и на подписчика ссылается только источник, то не обязательно отписываться.

Я представляю себе это как от дерева отпала ветка на которой есть другие веточки с листьями, внутри они сами на себя могут ссылаться сколь угодно, но т.к. нет ссылки на само дерево их породившее, то такая ветка будет собрана GC.
Ответ написан
Комментировать
Пригласить эксперта
Ответы на вопрос 1
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы