Случаются ситуации, когда данные должны быть обработаны эксклюзивно. Это значит, что среди немереного количества ваших параллельных задач вдруг встречается такая, которая может быть выполнена только при условии, что других задач в данный момент не обрабатывается.
Иными словами, нам нужно подождать, пока уже запущенные параллельные задачи закончатся, после чего запустить нашу эксклюзивную задачу, и только после ее завершения продолжить выполнение других параллельных задач.
Так же нередко встречаются ситуации, при которых нужно вообще перестать обрабатывать поступающие задачи. Например, пользователь нажал кнопку "отмена" и нам нужно перестать проверять ссылки на "живучесть".
Для разрешения таких ситуаций у класса Arbiter существует метод Interleave(..).
Пример его использования:
| 1. |
Arbiter.Activate(queue, |
| 2. |
Arbiter.Interleave( |
| 3. |
new TeardownReceiverGroup( |
| 4. |
Arbiter.Receive<EmptyValue>(false, stopPort, |
| 5. |
delegate(EmptyValue signal) { disp.Dispose(); } |
| 6. |
) |
| 7. |
), |
| 8. |
new ExclusiveReceiverGroup( |
| 9. |
Arbiter.Receive<int>(true, exclusivePort, StartExclusiveTask) |
| 10. |
), |
| 11. |
new ConcurrentReceiverGroup( |
| 12. |
Arbiter.Receive<int>(true, normalPort, StartParallelTask) |
| 13. |
) |
| 14. |
) |
| 15. |
); |
Метод Interleave(..) принимает на вход три параметра: три группы получателей-обработчиков.
Первая группа: TeardownReceiverGroup. Обработчики в этой группе не могут быть зарегистрированы "на постоянной основе", именно поэтому при регистрации получателя (строка 4) я указываю false. Это потому, что при "срабатывании" этой группы, то есть, как только один из получателей, зарегистрированных в ней, принимает задачу, прием других задач и запуск других обработчиков прекращается.
Имеются в виду только те обработчики, которые были зарегистрированы в том же методе Interleave, а не всех во всем приложении, естественно. Все другие обработчики для этой и других очередей продолжают работать.
Таким образом, группу TeardownReceiverGroup следует понимать как отмему регистрации всех получателей, сделанную в текущем методе Interleave. Что, в общем-то, и следует из ее названия.
Вторая группа: ExclusiveReceiverPort. Вот именно сюда и следует помещать обработчики задач, которые будут выполняться эксклюзивно. В моем примере при появлении значения в порту exclusivePort CCR подождет, пока завершатся уже запущенные задачи из группы ConcurrentReceiverPort (см. ниже), после чего вызовет метод StartExclusiveTask, передав ему полученное значение. И только после завершения StartExclusiveTask будет продолжен запуск параллельных задач из ConcurrentReceiverPort.
Обработчики в этой группе могут быть зарегистрированы "на постоянной основе".
Третья группа: ConcurrentReceiverPort. Это основная группа, где регистрируются обработчики, которые должны запускаться параллельно (что тоже следует из названия).
Обработчики в этой группе могут тоже быть зарегистрированы "на постоянной основе".
Итак, вернемся к задаче завершения приложения.
В моем простом случае я знаю, что пул не используется для других целей, поэтому мне достаточно просто "прибить" пул потоков при получении сигнала о завершении.
Поэтому в группе TeardownReceiverGroup я регистрирую обработчик "пустого значения", который, при появлении этого значения в порту stopPort просто вызывает метод Dispose у пула потоков.
В других случаях, если пул используется другими очередями, другими обработчиками и т.д. и алгоритм завершения будет другой :)
Видимо, для удобства в подобных ситуациях в CCR есть класс Shutdown, который представляет собой по сути набор портов (Success, Excepion) и экземпляр которого можно передавать куда-нибудь..