воскресенье, 12 июля 2009 г.

Подписка UI на события синглетона

Предположим, что у нас есть некоторое приложения с графическим интерфейсом (GUI). Пусть в приложении существует класс, являющийся синглетоном. Такая архитектура может быть продиктована логикой работы приложения: синглетон представляет собой объект, существующий в единственно экземпляре, он может соответствовать, например, очереди печати или отправки сообщений куда-либо, хранить настройки приложения. Пусть наш синглетон является источником событий (events), через которые он оповещает приложение о ходе выполнения неких операций. На эти события подписывается UI приложения и обеспечивает информирование пользователя.

Для моделирования этого взаимодействия создадим небольшое тестовое приложение (ссылка на исходный код в конце поста). Источником события синглетона является кнопка Trigger event главное формы, подписчиком - дочерние формы, создаваемые кнопкой Create a new child form. Данными, передаваемыми от сиглетона UI, является номер последней созданной дочерней формы.

Главное окно приложения выглядит весь незамысловато:



Приложение позволяет продемонстрировать два момент, о которых не следует забывать.

Первое. Создадим две дочерних формы и активируем событие синглетона.



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

Второй момент гораздо более интересен. Пользователь может закрыть дочернее окно, UI при этом будут уничтожен (будут вызваны методы Dispose()), но сами объекты останутся и будут продолжать получать события синглетона. Эти события будут для них корнями (root) и объекты не будут уничтожены сборщиком мусора. Попытка обновить уничтоженый UI может вызвать исключение. В данном демонстрационном приложении используется следующая конструкция:


using System;
using System.Windows.Forms;

namespace SingletonEvents
{
  public partial class ChildForm : Form
  {
    #region Fields

    private readonly int number;
    private readonly bool unsubscribeFromEvents;
    private bool disposed;

    #endregion Fields

    #region Constructors

    public ChildForm(int number, bool unsubscribeFromEvents)
    {
      InitializeComponent();

      this.number = number;
      this.unsubscribeFromEvents = unsubscribeFromEvents;

      Closing += ThisClosingEventHandler;
      Disposed += ThisDisposedEventHandler;
      Singleton.Instance.EventTriggered  += SingletonEventTriggeredEventsHandler;
    }

    #endregion Constructors

    #region Event handlers

    private void SingletonEventTriggeredEventsHandler(object sender, EventTriggeredEventsArgs e)
    {
      if (disposed)
      {
        string errorText = string.Format("Form #{0} has been already disposed", number);
        throw new InvalidOperationException(errorText);
      }

      MessageLabel.Text = string.Format("Form #{0} is active", e.ActiveChildFormNumber);
    }

    private void ThisClosingEventHandler(object sender, EventArgs e)
    {
      if (unsubscribeFromEvents)
      {
        Singleton.Instance.EventTriggered -= SingletonEventTriggeredEventsHandler;
      }
    }

    private void ThisDisposedEventHandler(object sender, EventArgs e)
    {
      disposed = true;
    }

    #endregion Event handlers
  }
}

Факт уничтожения UI определяется по флагу disposed, исключение при обработке события генерируется искусственно, приложение падает.


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

Исходный код здесь.

Комментариев нет:

Отправить комментарий