Собственная очередь событий: 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 ответов Оставить комментарий

  1. #1alex @ 2010-9-28 01:55 Ответ

    Добрый вечер.
    не подскажите, как будет правильно встроить ваш класс, в программу где есть закрытая dll, генерирующая события, и открытый код с обработчиками событий, соответственно, есть что-то вроде этого:

    dll.addsmth += addsmth;

    addsmth(double d, double b)
    {
    }

    насколько я понял, класс расчитан на встраивание работу с генераторами событий.

    зы. извините за вопрос новичка…

    • admin @ 2010-9-28 02:43 Ответ

      Добрый вечер.
      В этом случае можно использовать такую заглушку:
      dll.addsmth += (sender, e) => EventQueue<object>.Add(addsmth, sender, e);

Ответить alex

Отменить

(Ctrl + Enter)