Собственная очередь событий: EventQueue<T>
В ряде задач требуется, чтобы события вызывались строго последовательно, а не каскадно, как в классической реализации. Каскадность заключается в том, что, если в обработчике какого-либо события встретится вызов другого (или того же, не принципиально) события, то, собственно, обработка первого события прервется до завершения работы обработчика второго события. Это абсолютно нормальное и всех устраивающее поведение в подавляющем большинстве случаев, но не во всех.
Иногда может потребоваться изменить такое поведение очереди событий, чтобы, в результате, вызов второго обработчика происходил только по завершению работы первого.
Вашему вниманию предлагается статический класс, реализующий описанный подход:
C#: | Select code |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 | using System; using System.Collections.Generic; /// <summary> /// Статический класс, предназначенный для организации собственной очереди обработки событий. /// </summary> /// <typeparam name="T">Тип объектов, которые будут переданы обрабочикам событий в /// параметре sender.</typeparam> public static class EventQueue<T> where T : class { /// <summary> /// Режим работы очереди событий: /// <value>Queue</value> (режим очереди): новое событие будет обработано последним, /// <value>Stack</value> (режим стека): новое событие будет обработано первым, /// <value>Immediate</value> (классический режим): новое событие будет вызвано немедлено. /// </summary> public enum EventQueueMode { /// <summary> /// Режим очереди: новое событие будет обработано последним. /// </summary> Queue, /// <summary> /// Режим стека: новое событие будет обработано первым. /// </summary> Stack, /// <summary> /// Классический режим: новое событие будет вызвано немедлено. /// </summary> Immediate } /// <summary> /// Класс, являющийся элементом очереди событий. /// </summary> public sealed class Event : IEquatable<Event> { private readonly Delegate[] handlers; private readonly T sender; private readonly EventArgs args; public Event(Delegate Handler, T Sender, EventArgs Args) : this(Handler == null ? null : Handler.GetInvocationList(), Sender, Args) { } internal Event(Delegate[] Handlers, T Sender, EventArgs Args) { if (Handlers == null) throw new ArgumentNullException("Handlers"); handlers = Handlers; sender = Sender; args = Args; } internal void Invoke() { if (Sorter != null && handlers.Length > 1) Array.Sort(handlers, Sorter); foreach (Delegate d in handlers) d.DynamicInvoke(new object[] {sender, args}); } public bool Equals(Event other) { return handlers == other.handlers && sender == other.sender && args == other.args; } } /// <summary> /// Если значение данного поля не null, то обработчики события перед вызовом будут /// отсортированы при помощи делегата, указанного в данном поле. /// </summary> public static Comparison<Delegate> Sorter; private static bool CanRun = true; private static bool running; private static readonly LinkedList<Event> events = new LinkedList<Event>(); private static EventQueueMode queueMode; private static Action<Event> addFunctor; static EventQueue() { QueueMode = EventQueueMode.Queue; } /// <summary> /// Добавить новое событие в очередь. /// </summary> /// <param name="Handler">Событие (event).</param> /// <param name="Sender">Объект, вызвавший событие.</param> /// <param name="Args">Параметры события.</param> public static void Add(Delegate Handler, T Sender, EventArgs Args) { Add(new Event(Handler, Sender, Args)); } /// <summary> /// Добавить новое событие в очередь. /// </summary> /// <param name="newEvent">Событие (EventPool.Event).</param> public static void Add(Event newEvent) { if (events.Contains(newEvent)) return; addFunctor(newEvent); if (CanRun && events.Count == 1 && !running) Resume(); } /// <summary> /// Приостановить вызовы. /// </summary> public static void Suspend() { CanRun = false; } /// <summary> /// Возобновить вызовы. /// </summary> public static void Resume() { CanRun = true; while (events.Count != 0 && CanRun) { Event e = events.First.Value; events.RemoveFirst(); running = true; e.Invoke(); running = false; } } /// <summary> /// Возвращает количество событий, находящихся в очереди. /// </summary> public static int Count { get { return events.Count; } } /// <summary> /// Возвращает/устанавливает режим работы очереди обработчиков событий. /// Значение по-умолчанию: <value>Queue</value>. /// См. <see cref="EventQueueMode"/>. /// </summary> /// <exception cref="ApplicationException">Если при установке режима /// <see cref="EventQueueMode.Immediate"/> очередь событий не пуста.</exception> public static EventQueueMode QueueMode { get { return queueMode; } set { queueMode = value; switch (value) { case EventQueueMode.Immediate: if (events.Count != 0) throw new ApplicationException("Event queue is not empty!"); addFunctor = e => e.Invoke(); break; case EventQueueMode.Queue: addFunctor = e => events.AddLast(e); break; case EventQueueMode.Stack: addFunctor = e => events.AddFirst(e); break; } } } } |
Данная очередь событий может работать в трех режимах (за это отвечает свойство QueueMode):
- Queue (режим очереди — по-умолчанию): новое событие будет обработано последним.
- Stack (режим стека): новое событие будет обработано первым.
- Immediate (классический режим): новое событие будет вызвано немедлено.
Более подробное описание смотрите в комменариях к членам класса.
Хочется отметить, что, поскольку для каждого типа T в статическом контексте создается свой экземпляр класса EventQueue<T>, то, следовательно, для каждого типа, вызывающего событие, можно организовать свою очередь событий, работающую в своем режиме. Правда в подавляющем большинстве случаев это, мягко говоря, нецелесообразно. В этих случаях имеет смысл использовать единую очередь событий, например: EventQueue<object>.
Пример использования.
Данный класс следует использовать в качестве замены стандарных вызовов:
C#: | Select code |
if (some_event_handler != null) some_event_handler(this, new some_EventArgs(...));
на такие конструкции:
C#: | Select code |
if (some_event_handler != null) EventQueue<object>.Add(some_event_handler, this, new some_EventArgs(...));
Если дополнительно требуется, чтобы обработчики событий были отсортированы перед вызовом, используйте статическое поле EventQueue<T>.Sorter.
Удачи!
Обсуждение на форуме: http://usings.ru/forum/viewtopic.php?t=10
2 ответов Оставить комментарий
Добрый вечер.
не подскажите, как будет правильно встроить ваш класс, в программу где есть закрытая dll, генерирующая события, и открытый код с обработчиками событий, соответственно, есть что-то вроде этого:
dll.addsmth += addsmth;
addsmth(double d, double b)
{
}
насколько я понял, класс расчитан на встраивание работу с генераторами событий.
зы. извините за вопрос новичка…
Добрый вечер.
В этом случае можно использовать такую заглушку:
dll.addsmth += (sender, e) => EventQueue<object>.Add(addsmth, sender, e);