Qwen, Giga, TplDataflow, WorkerServiceSln.slnx
https://giga.chat/link/gcsjaPOepG
---------------------------------------------------------------------------
D:\Projects\VS02\2606\TplDataflow\Qwen\WorkerServiceSln\WorkerServiceSln.slnx
D:\Projects\VS02\2606\TplDataflow\Qwen\WorkerServiceSln\WorkerService\WorkerService.csproj
----------------------------------------------------------------------------------
Subscriber
----------------------------------------------------------------------------------
Этот код представляет собой реализацию фонового сервиса (BackgroundService) в .NET, который работает как подписчик в системе обмена сообщениями. Сервис получает котировки (Quote) от издателя через канал, обрабатывает их по конвейеру и отправляет обратную связь.
Ключевую роль в архитектуре этого сервиса играет библиотека TPL Dataflow (System.Threading.Tasks.Dataflow). Она предоставляет набор примитивов для построения конвейеров обработки данных (dataflow pipelines), которые работают на основе задач (Tasks) и асинхронности.
Рассмотрим, как именно эти абстракции реализованы в коде и какие задачи они решают.
1. Конвейерная обработка (Pipeline)
Вместо того чтобы обрабатывать каждую котировку в одном методе, код разбивает процесс на логические этапы. Это классический паттерн "конвейер" или "пайплайн".
Этап 1: Валидация (
_validationBlock)- Тип блока:
TransformBlock<Quote, Quote>. - Назначение: Принимает объект
Quoteна вход и возвращает либо валидированныйQuote, либоnull(в случае ошибки). - Реализация: Внутри блока выполняется метод
ValidateQuote. Здесь проверяется бизнес-логика (например, что цена больше нуля). Если котировка невалидна, возвращаетсяnull. - Преимущество TPL Dataflow: Блок инкапсулирует логику преобразования и может работать параллельно. Параметр
MaxDegreeOfParallelism = 3означает, что до 3 котировок могут проходить валидацию одновременно.
- Тип блока:
Этап 2: Обогащение (
_enrichmentBlock)- Тип блока:
TransformBlock<Quote, Quote>. - Назначение: Принимает валидированный объект
Quoteи возвращает его же, но с измененными или дополненными данными. - Реализация: Метод
EnrichQuoteокругляет цену до двух знаков после запятой. - Преимущество TPL Dataflow: Этот блок автоматически получает на вход только те данные, которые успешно прошли предыдущий этап. Он также может работать параллельно (
MaxDegreeOfParallelism = 2).
- Тип блока:
Этап 3: Обработка (
_processingBlock)- Тип блока:
ActionBlock<Quote>. - Назначение: Принимает объект
Quoteи выполняет с ним какое-то действие. Он не возвращает результат в конвейер. - Реализация: Метод
ProcessQuote. Здесь происходит основная работа: логирование, имитация сохранения в БД. Также здесь формируется ответ для издателя. - Преимущество TPL Dataflow: Идеально подходит для финальных действий в конвейере. Работает параллельно (
MaxDegreeOfParallelism = 4).
- Тип блока:
Этап 4: Отправка ответа (
_responseBlock)- Тип блока:
ActionBlock<Quote>. - Назначение: Отправляет сгенерированную котировку обратно издателю по другому каналу.
- Реализация: Асинхронный метод
SendResponseAsync. - Преимущество TPL Dataflow: Этот блок изолирует потенциально медленную или нестабильную операцию (сетевой вызов) от основного потока обработки. Он имеет
MaxDegreeOfParallelism = 1, что гарантирует последовательную отправку ответов, что может быть важно для сохранения порядка или ограничения нагрузки на канал обратной связи.
- Тип блока:
2. Связывание блоков и управление потоком
Блоки не работают изолированно. Их связывает метод .LinkTo(). Это одна из самых мощных возможностей TPL Dataflow.
Соединение конвейера:
_validationBlock.LinkTo(_enrichmentBlock, linkOptions);_enrichmentBlock.LinkTo(_processingBlock, linkOptions, quote => quote != null);Данные автоматически перетекают из одного блока в другой. Как только_validationBlockзавершает обработку элемента, он передает его в_enrichmentBlock.Фильтрация данных: Обратите внимание на второй вызов
.LinkTo(). В нем передан предикат:quote => quote != null. Это критически важный момент. Если валидация вернулаnull, этот элемент будет отфильтрован и не попадет в блок обогащения и дальнейшей обработки. Это позволяет элегантно обрабатывать ошибки без необходимости писать сложные проверки внутри самих блоков.Управление буферизацией (
BoundedCapacity): У каждого блока есть параметрBoundedCapacity. Например, у_processingBlockон равен 10. Это означает, что блок может одновременно хранить в своем внутреннем буфере не более 10 котировок, ожидающих обработки.- Если буфер полон, блок "отказывается" принимать новые элементы от предыдущего блока.
- Это создает механизм обратного давления (backpressure). Если этап обработки самый медленный (бутылочное горлышко), он замедлит всю систему, не допуская переполнения памяти неконтролируемым потоком входящих данных.
3. Интеграция с внешними источниками
TPL Dataflow блоки могут быть связаны не только друг с другом, но и с внешними источниками/приемниками данных.
_channel.PublisherToSubscriber.LinkTo(_validationBlock, linkOptions);
Здесь блок _validationBlock напрямую подписывается на ISourceBlock<Quote> (которым является канал от издателя). Как только в канале появляется новая котировка, она автоматически передается на вход конвейера обработки. Это избавляет от необходимости вручную писать цикл while (!stoppingToken.IsCancellationRequested) с вызовом ReceiveAsync().
4. Управление жизненным циклом и отказоустойчивость
- Пропагация завершения (
PropagateCompletion = true): Когда источник данных (канал) завершает работу (вызывает.Complete()), это состояние автоматически передается по всей цепочке блоков. Каждый блок в конвейере получит сигнал о завершении и корректно завершит свою работу после обработки оставшихся в буфере элементов. - Грандиозная остановка (
StopAsync): В методе остановки сервиса мы вызываем.Complete()для каждого блока конвейера и ждем их завершения черезTask.WhenAll(...). Это гарантирует, что сервис не будет остановлен до тех пор, пока все котировки не будут полностью обработаны и отправлены.
Итог
В этом коде TPL Dataflow реализует следующие ключевые концепции:
- Конвейерная архитектура: Разбиение сложной задачи на простые, независимые этапы (валидация, обогащение, обработка).
- Асинхронность и параллелизм "из коробки": Каждый этап может обрабатывать данные параллельно с настраиваемой степенью параллелизма, эффективно используя ресурсы процессора.
- Буферизация и обратное давление: Защита системы от перегрузки с помощью ограниченных по размеру буферов (
BoundedCapacity). - Декларативные потоки данных: Вместо императивного управления потоком выполнения (циклы, блокировки) используется декларативное связывание блоков (
LinkTo), что делает код чище и надежнее. - Надежное управление жизненным циклом: Автоматическая передача сигналов о завершении и корректная остановка всех компонентов системы.
Таким образом, TPL Dataflow здесь выступает как мощный фреймворк для построения надежных, масштабируемых и легко поддерживаемых систем обработки потоков данных.
---------------------------------
Короткий ответ: данные передаются через канал (QuoteChannel), который является частью паттерна Publish/Subscribe. Блоки ActionBlock и TransformBlock — это внутренние компоненты подписчика, которые обрабатывают данные после того, как они были получены из канала.
Детальное объяснение
Представьте два независимых сервиса (приложения), которые работают в разных процессах или даже на разных серверах:
- Publisher Service (Издатель) — генерирует котировки.
- Subscriber Service (Подписчик) — получает, валидирует и обрабатывает эти котировки.
Чтобы они могли общаться, им нужен "мостик" или "труба". В .NET для такой межпроцессной коммуникации часто используются технологии вроде RabbitMQ, Apache Kafka, Azure Service Bus или Redis. Абстракция над таким "мостиком" и называется шиной сообщений (Message Bus).
В вашем коде роль такого "мостика" выполняет объект _channel, а именно его свойство PublisherToSubscriber.
Как происходит передача данных?
На стороне Издателя (Publisher): Издатель берет готовую котировку и отправляет её в канал, предназначенный для подписчиков.
csharp// Логика внутри PublisherService await _channel.PublisherToSubscriber.SendAsync(quote);(Примечание: сам код издателя не показан, но подразумевается).
Межсервисное взаимодействие: Котировка покидает процесс Издателя и попадает в общую очередь (брокер сообщений). Оттуда она становится доступной для Подписчиков.
На стороне Подписчика (Subscriber): Ваш сервис-подписчик имеет метод
ExecuteAsync, где настраивается конвейер обработки. Ключевая строка здесь:_channel.PublisherToSubscriber.LinkTo(_validationBlock, linkOptions);Эта строка делает следующее: она "подключает" вход вашего внутреннего конвейера (
_validationBlock) к внешнему источнику данных (_channel.PublisherToSubscriber).Теперь, когда в канале появляется новая котировка от Издателя, TPL Dataflow автоматически забирает её и передает на вход первому блоку вашего конвейера —
_validationBlock. С этого момента данные движутся внутри сервиса-подписчика.
Роль внутренних блоков (ActionBlock, TransformBlock)
Теперь посмотрим на ваш список и определим, что есть что:
- Channels (
_channel.PublisherToSubscriber): Это компонент для передачи данных между сервисами. Он реализует паттерн Publish/Subscribe. - TransformBlock (
_validationBlock,_enrichmentBlock): Это компоненты для обработки данных внутри одного сервиса. Они принимают данные на вход, выполняют с ними какую-то операцию (ValidateQuote,EnrichQuote) и передают результат дальше по конвейеру. - ActionBlock (
_processingBlock,_responseBlock): Это тоже компоненты для обработки данных внутри одного сервиса. Они являются финальной точкой в конвейере (или одной из них). Принимают данные и просто выполняют действие (логируют, сохраняют в БД, отправляют ответ), ничего не возвращая в поток.
Аналогия
Представьте почтовую систему:
- Каналы (
Channels) — это курьерская служба (например, DHL). Она перевозит посылки из одного города (сервиса-издателя) в другой (сервис-подписчик). - Блоки (
TransformBlock,ActionBlock) — это рабочие на складе в городе-получателе. Когда посылка прибывает, один рабочий проверяет её содержимое (Validation), второй наклеивает штрих-код (Enrichment), третий кладёт на полку (Processing), а четвёртый отправляет уведомление отправителю об успешной доставке (Response).
Рабочие общаются друг с другом по конвейерной ленте внутри склада, но сама посылка попала к ним благодаря внешней курьерской службе.
Итог
Данные из одного сервиса в другой передаются через каналы (channels). Внутренние блоки ActionBlock и TransformBlock являются лишь звеньями в цепочке обработки этих данных уже после их получения внутри принимающего сервиса
---------------------------------------------------------------------------------
PublisherService
---------------------------------------------------------------------------------
Отлично, давайте разберем код PublisherService. Этот сервис выполняет две основные функции: он является издателем (отправляет данные) и одновременно подписчиком на канал обратной связи.
Это классический пример двунаправленного взаимодействия. Если в SubscriberService мы видели только входящий поток данных, то здесь реализован полный цикл "Запрос-Ответ".
1. Издательская часть (Outbound / Publisher)
Эта часть кода отвечает за генерацию и отправку котировок подписчикам.
Цикл публикации (
RunQuotePublicationLoop) Внутри бесконечного циклаwhileсервис:- Генерирует случайную котировку с помощью
GenerateRandomQuote. - Отправляет её через
_channel.PublisherToSubscriber.SendAsync(quote). Это та самая "труба", по которой данные уходят к подписчику. - Публикует копию котировки для целей мониторинга через
_channel.MonitoringBroadcast.Post(quote). Это позволяет другим частям системы (например, дашборду) отслеживать все генерируемые котировки без влияния на основной рабочий процесс. - Ждет 500 мс перед следующей итерацией.
- Генерирует случайную котировку с помощью
Генерация данных (
GenerateRandomQuote) Простой метод, который создает объектQuoteсо случайным символом акции и ценой. Важно, что полеSourceустановлено в"Publisher", чтобы можно было идентифицировать происхождение сообщения.
2. Подписчик на обратную связь (Inbound / Subscriber)
Эта часть кода показывает, как издатель слушает ответы от обработчика (в нашем случае — от SubscriberService).
Блок обработки входящих ответов (
_incomingQuotesBlock) В методеSetupIncomingQuotesProcessingсоздается блокActionBlock<Quote>. Его задача — просто принимать входящие объектыQuoteи логировать их.- Связывание: Самое важное происходит здесь:
_channel.SubscriberToPublisher.LinkTo(_incomingQuotesBlock, ...)Издатель подключает свой внутренний блок_incomingQuotesBlockк каналу_channel.SubscriberToPublisher. Это тот самый канал, по которомуSubscriberServiceотправлял свои ответы (вспомнитеSendResponseAsync).
- Связывание: Самое важное происходит здесь:
Обработка ответа (
ProcessIncomingQuote) КогдаSubscriberServiceформирует ответную котировку (каждую пятую), он отправляет её в каналSubscriberToPublisher. TPL Dataflow автоматически доставляет этот объект в блок_incomingQuotesBlock, который вызывает методProcessIncomingQuote. Таким образом, издатель получает подтверждение, что его данные были успешно обработаны.
Ключевые отличия от SubscriberService
| Характеристика | PublisherService | SubscriberService |
|---|---|---|
| Основная роль | Издатель. Создает и отправляет данные. | Подписчик. Принимает и обрабатывает данные. |
| Направление потока | Исходящий (PublisherToSubscriber). | Входящий (PublisherToSubscriber). |
| Обратная связь | Является подписчиком на канал SubscriberToPublisher. | Является издателем в канале SubscriberToPublisher. |
| Внутренний конвейер | Очень простой: один блок ActionBlock для логирования ответов. | Сложный: цепочка из TransformBlock и ActionBlock для валидации, обогащения и обработки. |
Итог
Код PublisherService реализует полноценный двусторонний обмен данными:
- Шаг 1 (Publish):
PublisherService->PublisherToSubscriberChannel ->SubscriberService. - Шаг 2 (Process):
SubscriberServiceобрабатывает котировку. - Шаг 3 (Respond/Publish):
SubscriberService->SubscriberToPublisherChannel ->PublisherService. - Шаг 4 (Subscribe/Receive):
PublisherServiceполучает ответ через свой внутренний блок_incomingQuotesBlock.
Таким образом, TPL Dataflow используется здесь не только для построения внутренних конвейеров обработки, но и для элегантного связывания независимых сервисов в единую систему обмена сообщениями
---------------------------------------------------------------------------------------------------
MonitoringService
----------------------------------------------------------------------------------------------------
Этот сервис, MonitoringService, выполняет роль наблюдателя или слушателя в системе. Он не участвует в основной цепочке обработки котировок (как Publisher и Subscriber), а работает параллельно, собирая статистику и предоставляя информацию о состоянии всей системы в реальном времени.
Это классический пример паттерна "Наблюдатель" (Observer), реализованного с помощью TPL Dataflow.
Что именно делает этот сервис?
Его можно разделить на две основные функции: сбор данных и генерация отчетов.
1. Сбор данных (Подписка на поток)
Сервис подписывается на специальный канал _channel.MonitoringBroadcast. Это делается в методе ExecuteAsync:
_channel.MonitoringBroadcast.LinkTo(_monitoringBlock, ...);- Источник данных: В отличие от
PublisherService, который отправляет данные в_channel.PublisherToSubscriber, он также копирует каждую сгенерированную котировку в_channel.MonitoringBroadcastс помощью методаPost. - Действие: Когда новая котировка попадает в этот широковещательный канал, блок
_monitoringBlock(типActionBlock<Quote>) получает её и вызывает методMonitorQuote. - Логика
MonitorQuote: Этот метод выполняет следующие действия:- Увеличивает общий счетчик обработанных котировок (
_totalQuotes). - Сохраняет цену каждой котировки в словарь
_priceHistory. Ключ словаря — это символ акции (например, "AAPL"), а значение — список последних 100 цен. - Важно: Он хранит только скользящее окно из 100 последних значений для каждого символа, чтобы не переполнить память и анализировать только актуальные данные.
- Увеличивает общий счетчик обработанных котировок (
2. Генерация отчетов (Периодическая задача)
Сервис не просто пассивно собирает данные, но и периодически выводит сводную информацию. Это происходит в методе RunPeriodicReportsAsync.
- Цикл: Метод запускает бесконечный цикл с задержкой в 10 секунд (
await Task.Delay(TimeSpan.FromSeconds(10))). - Формирование отчета: Каждые 10 секунд сервис захватывает текущие данные из
_priceHistoryи_totalQuotes(используя блокировкуlockдля обеспечения потокобезопасности) и вычисляет статистику для каждого символа акции:- Текущая цена: Последнее значение в списке.
- Средняя цена: Среднее арифметическое за последние 100 котировок.
- Минимальная и максимальная цена: Диапазон цен за последние 100 котировок.
- Волатильность: Рассчитывается как процентное соотношение диапазона (
max - min) к средней цене. Это хороший показатель того, насколько сильно колеблется цена.
- Логирование: Вся собранная статистика красиво форматируется и выводится в лог с помощью
_logger.LogInformation.
Ключевые особенности реализации
- Разделение потоков: Мониторинг не мешает основному конвейеру обработки. Это "неинтрузивный" способ наблюдения за системой.
- Широковещательный канал (
BroadcastBlock): Использование_channel.MonitoringBroadcastпозволяет иметь несколько независимых подписчиков (например, один для логирования, другой для дашборда), при этом каждый подписчик будет получать свою копию сообщения. - Потокобезопасность: Поскольку данные о котировках поступают асинхронно, а отчет генерируется по таймеру, доступ к общим переменным (
_priceHistory,_totalQuotes) защищен с помощью конструкцииlock (_lock). Это предотвращает состояние гонки. - Изоляция обработки: Блок
_monitoringBlockнастроен на последовательную обработку (MaxDegreeOfParallelism = 1). Это гарантирует, что статистика будет собираться корректно, без одновременного доступа к словарю из разных потоков.
Итог
MonitoringService — это сервис телеметрии. Он "подсматривает" за потоком данных, проходящим через систему, агрегирует их и предоставляет наглядную картину того, что происходит: каков общий объем данных, по каким инструментам идут торги и насколько волатильны цены. Это критически важный компонент для отладки и контроля работы любой системы обработки данных в реальном времени.
---------------------------------------------------------------------------------------------
ActionBlock
---------------------------------------------------------------------------------------------
Давайте разберем ActionBlock максимально подробно. Это один из самых часто используемых и фундаментальных блоков в TPL Dataflow.
Если представить TPL Dataflow как конструктор для сборки конвейеров, то ActionBlock — это «потребитель» или «конечная точка» (sink) вашего конвейера.
1. Чем он является?
ActionBlock — это класс из пространства имен System.Threading.Tasks.Dataflow, который реализует интерфейс ITargetBlock<TInput>.
- Что это значит простыми словами: Он является «целью» для данных. Его главная задача — принимать на вход объекты определенного типа (
TInput) и что-то с ними делать. - Аналогия: Представьте почтовый ящик. Вы кладете в него письмо (данные). Человек (ваш код), который проверяет ящик, достает письмо и выполняет с ним действие (читает, выбрасывает, кладет в архив). Сам ящик — это
ActionBlock.
В отличие от TransformBlock, который принимает данные и возвращает новые данные в конвейер, ActionBlock — это «черная дыра». Он принимает данные и не возвращает ничего. Поток данных на нем заканчивается.
2. Что он делает?
Внутри себя ActionBlock делает три основные вещи:
- Принимает данные: Он получает объекты (например, котировки
Quote) из предыдущего блока в цепочке или из внешнего источника. - Выполняет делегат: Для каждого полученного объекта он вызывает указанный вами метод (делегат). Это и есть то самое «действие».
- В коде это выглядит так:
new ActionBlock<Quote>(quote => ProcessQuote(quote)); - Здесь
ProcessQuote— это метод, который вы написали. Блок просто вызывает его для каждого входящегоQuote.
- В коде это выглядит так:
- Управляет параллелизмом: Он может вызывать ваш метод несколько раз одновременно (если вы разрешили параллелизм), используя пул потоков. Это позволяет обрабатывать поток данных очень быстро, не дожидаясь завершения одного действия для начала следующего.
3. Зачем он нам нужен?
ActionBlock незаменим для выполнения финальных или побочных эффектов в конвейере обработки данных. В вашем коде он используется для нескольких ключевых задач:
Завершение конвейера: Это самая частая роль. Конвейер должен где-то закончиться.
ActionBlockидеально подходит для финального шага, когда данные больше не нужно никуда передавать, а нужно просто выполнить действие.- Пример: В
SubscriberServiceблок_processingBlockявляется концом основного конвейера. Котировка пришла, ее проверили, обогатили, и теперь ее надо просто обработать (например, сохранить в базу данных или отправить на API). После этого она больше не нужна в памяти конвейера.
- Пример: В
Отправка ответов (Обратная связь): Когда вам нужно отправить результат работы во внешний мир (в другой сервис, по сети).
- Пример: Блок
_responseBlockвSubscriberService. Он не передает данные дальше по внутреннему конвейеру. Его задача — взять готовую котировку и отправить ее через канал обратно издателю.
- Пример: Блок
Прием входящих данных: Он работает не только как «потребитель», но и как «слушатель».
- Пример: Блок
_incomingQuotesBlockвPublisherService. Он стоит в самом начале своего маленького конвейера и просто ждет, когда из канала обратной связи придет котировка от подписчика, чтобы ее обработать (в данном случае — просто залогировать).
- Пример: Блок
Сбор метрик и логирование: Идеальное место для сбора статистики без влияния на основной поток данных.
- Пример: Блок
_monitoringBlockвMonitoringService. Он получает копию каждой котировки и просто обновляет счетчики и историю цен. Это действие не меняет саму котировку и не требует передачи ее дальше.
- Пример: Блок
Краткий итог:
ActionBlock нужен нам, чтобы сказать системе: "Вот этот кусок кода нужно выполнить для каждого входящего элемента данных". Он берет на себя всю сложную работу по управлению очередями, буферизацией и многопоточностью, позволяя нам сосредоточиться на логике самого действия.
---------------------------------------------------------------------------------------------
Tpl Dataflow
---------------------------------------------------------------------------------------------
В коде используются четыре основных типа примитивов TPL Dataflow.
1. TransformBlock<TInput, TOutput>
- Что делает: Принимает на вход объект типа
TInput, выполняет над ним асинхронную (или синхронную) операцию и возвращает объект типаTOutput. Это "трансформирующий" блок. - Где используется:
- В
SubscriberService:_validationBlock: ПринимаетQuote, проверяет его валидность. Если всё хорошо, возвращает тот жеQuote. Если нет — возвращаетnull._enrichmentBlock: Принимает валидныйQuoteи обогащает его (в данном коде просто округляет цену), возвращая измененныйQuote.
- В
2. ActionBlock<T>
- Что делает: Принимает на вход объект типа
Tи выполняет над ним некоторое действие (метод). Этот блок не возвращает результат, он является "конечной точкой" или промежуточным действием в конвейере. - Где используется:
- В
SubscriberService:_processingBlock: ПринимаетQuoteи выполняет его основную обработку (имитация анализа, логирование)._responseBlock: ПринимаетQuoteи отправляет его обратно издателю через канал обратной связи.
- В
PublisherService:_incomingQuotesBlock: Принимает ответныеQuoteот подписчика и просто логирует факт их получения.
- В
MonitoringService:_monitoringBlock: ПринимаетQuoteи сохраняет его цену в историю для последующего расчета статистики.
- В
3. BufferBlock<T>
- Что делает: Это базовый блок, который реализует интерфейс
ISourceBlock<T>иITargetBlock<T>. Он служит буфером (очередью) для хранения сообщений. Другие блоки могут отправлять в него данные (Post/SendAsync) и забирать их оттуда (Receive). - Где используется:
- Везде неявно: Этот примитив является фундаментом для всех остальных. В вашем коде он используется внутри реализации
QuoteChannel. Каналы (PublisherToSubscriber,SubscriberToPublisher) — это, по сути, и естьBufferBlock<Quote>, которые обеспечивают передачу данных между разными процессами или сервисами.
- Везде неявно: Этот примитив является фундаментом для всех остальных. В вашем коде он используется внутри реализации
4. BroadcastBlock<T>
- Что делает: Принимает сообщение и клонирует его для всех связанных с ним подписчиков. Если к нему подключено три блока, он отправит копию сообщения каждому из них.
- Где используется:
- В
MonitoringService: _channel.MonitoringBroadcast: Это и есть примитивBroadcastBlock<Quote>. Он позволяет сервису мониторинга получать копии всех котировок, которые публикуетPublisherService, не мешая при этом основному потоку данных, идущему в_channel.PublisherToSubscriber.
Итоговая таблица
| Примитив TPL Dataflow | Что делает в коде | Где используется |
|---|---|---|
| TransformBlock | Преобразует данные. Валидирует и обогащает котировки. | SubscriberService (Validation, Enrichment) |
| ActionBlock | Выполняет действие. Обрабатывает, отвечает, логирует, собирает статистику. | Все три сервиса (Processing, Response, Incoming, Monitoring) |
| BufferBlock | Хранит данные в очереди, служит мостом для передачи между сервисами. | В основе каналов QuoteChannel |
| BroadcastBlock | Копирует данные для нескольких подписчиков. | MonitoringService (через _channel.MonitoringBroadcast) |
Комментариев нет:
Отправить комментарий