Подтвердить что ты не робот

Обработка событий задержки до тех пор, пока не будут запущены события

В С#, какой лучший способ отложить обработку всех известных событий до тех пор, пока объект не будет полностью изменен? Скажем, например, что объект - MyEntity - имеет идентификаторы свойств, имя и описание...

   public class MyEntity
   {
       public Int32 ID { get; set; }
       public String Name { get; set; }
       public String Description { get; set; }
   }

При изменении каждого из этих свойств событие запускается для каждой модификации.

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

Каков наилучший способ для этого?

В моей голове что-то похожее на шаблон UnitOfWork, где можно заключить оператор using вокруг вызова метода на верхнем уровне стека вызовов, но не имеет понятия, как реализовать такую ​​вещь...

Изменить: В качестве пояснения... Слушатели распространяются через приложение и выполняются в других потоках. Другой актер устанавливает, например, имя, которое должно вызывать свойство MyEntity.Name, чтобы установить значение.

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

4b9b3361

Ответ 1

Только код, выполняющий изменения, может знать, когда завершена его партия изменений.

То, что я сделал с моими аналогичными классами, заключается в предоставлении методов SuspendNotifications() и ResumeNotifications(), которые вызываются очевидным образом (т.е. вызывают приостановку перед тем, как сделать кучу изменений, при необходимости вызовите резюме).

Они внутренне поддерживают счетчик, который увеличивается в SuspendNotifications() и уменьшается с помощью ResumeNotifications(), и если декремент приводит к нулю, выдается уведомление. Я сделал это так, потому что иногда мне нужно было модифицировать некоторые свойства, а затем вызвать другой метод, который изменил еще несколько, и который сам будет вызывать suspend/resume.

(Если резюме вызвано слишком много раз, я выбрал исключение.)

Если изменено несколько свойств, окончательное уведомление не называет измененное свойство (поскольку их несколько). Я полагаю, вы могли бы накапливать список измененных свойств и выпускать это как часть уведомления, но это не очень полезно.

Также обратите внимание, что безопасность потока может быть или не быть проблемой для вас. Возможно, вам придется использовать блокировку и/или Interlocked.Increment() и т.д.

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

Код может выглядеть так:

public void DoStuff()
{
    try
    {
        _entity.SuspendNotifications();
        setProperties();
    }

    finally
    {
        _entity.ResumeNotifications();
    }
}

private setProperties()
{
    _entity.ID = 123454;
    _entity.Name = "Name";
    _entity.Description = "Desc";
}

[EDIT]

Если бы вы представили интерфейс, скажем ISuspendableNotifications, вы можете написать класс оболочки IDisposable для упрощения.

Пример ниже иллюстрирует концепцию; Использование NotificationSuspender упрощает (фактически удаляет) логику try/catch.

Обратите внимание, что class Entity, конечно, фактически не реализует приостановку/возобновление или не обеспечивает обработку ошибок; который оставил в качестве пресловутого упражнения для читателя.:)

using System;

namespace Demo
{
    public interface ISuspendableNotifications
    {
        void SuspendNotifications();
        void ResumeNotifications();
    }

    public sealed class NotificationSuspender: IDisposable
    {
        public NotificationSuspender(ISuspendableNotifications suspendableNotifications)
        {
            _suspendableNotifications = suspendableNotifications;
            _suspendableNotifications.SuspendNotifications();
        }

        public void Dispose()
        {
            _suspendableNotifications.ResumeNotifications();
        }

        private readonly ISuspendableNotifications _suspendableNotifications;
    }

    public sealed class Entity: ISuspendableNotifications
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }

        public void SuspendNotifications() {}
        public void ResumeNotifications() {}
    }

    public static class Program
    {
        public static void Main(string[] args)
        {
            Entity entity = new Entity();

            using (new NotificationSuspender(entity))
            {
                entity.Id = 123454;
                entity.Name = "Name";
                entity.Description = "Desc";
            }
        }
    }
}

Ответ 2

Я мог бы предложить

public class MyEntity
{
    private const int FieldsCount = 3;

    private Int32 id;
    private String name;
    private String description;

    private HashSet<string> dirty = new HashSet<string>();

    public Int32 ID
    {
        get { return id; }
        set
        {
            id = value;
            dirty.Add("id");
            GoListeners();
        }
    }

    //...

    private void GoListeners()
    {
        if (dirty.Count == FieldsCount)
        {
            //...
            dirty.Clear();
        }
    }

}

Ответ 3

Я думаю, что это будет непросто, поскольку события запускаются асинхронно, но обрабатываются синхронно потоком исполнения. Одна из возможностей - использовать AutoResetEvent или ManualResetEvent и использовать метод WaitOne, чтобы дождаться, когда Set освободит его.
Вам, вероятно, понадобится использовать его в сочетании с Mutex. Но если вы работаете только в одном потоке, это не сработает.

Смотрите здесь для ManualResetEvent и здесь для AutoResetEvent.

Ответ 4

Предполагая, что все ваши события используют одну и ту же подпись:

  • Инициализировать делегат, например eventQueue при создании экземпляра MyEntity, и значение int, например 'queueRequiredLength'
  • Каждый установщик свойств добавляет свое событие в очередь, если он уже не присутствует, eventQueue += newEvent; вместо того, чтобы просто отключить событие.
  • Каждый определитель свойств затем проверяет длину очереди и запускает делегат (т.е. все события в очереди) if(length == queueRequiredLength) {eventQueue();}

(В верхней части моей головы я не уверен, как проверить количество методов, "поставленных в очередь" в делегате, но в худшем случае вы также можете просто держать счетчик и увеличивать его с каждым добавлением в очередь).