Как сделать код контекстно-зависимым, но при этом не передавать контекст явно?

164
16 мая 2019, 15:30

Что есть сейчас:

public static Page GetPage(Uri url)

Что нужно:

Ограничить число запросов в рамках определенных "контекстов".

  1. Это может быть host в параметре url (и это самый просто вариант, можно просто записывать текущие хосты и ограничивать)
  2. Может быть "сайт", который хостит разные вещи на разных инстансах облачного провайдера и по хосту их уже не отличить.
  3. Может быть сборка запрашивающая метод (т.е. сборке например нельзя этим методом получать больше чем 3 разных страницы в один момент времени)
  4. Может быть метод (метод не должен например параллельно делать большую пачку запросов), хотя возможно входит в предыдущий вариант, не продумывал пока детально.

В целом, с одной стороны хочется чтобы когда я делаю условный godObject.Download(), а тот делает какие то свои проверки внутри, плюс скачивание, плюс ещё чего-нибудь, чтобы внутри не возникала куча запросов параллельных, с другой - чтобы когда я делаю simpleClass.DownloadAllLinks() он мог обработать параллельно пачку ссылок, если на разных ресурсах находятся.

Видится это примерно как свойство Request, которое в рамках вебсервера есть практически всегда, т.к. обработка запроса и есть весь жизненный цикл. У меня цикл приложения в любом случае какой то тоже есть, например нажатие кнопки пользователем (но оно не будет к сожалению контекстом для скачивания страничек). Но сложить всё это в какую то логическую схему пока не могу.

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

Answer 1

Основная идея мне пришла давно и в целом, её реализуемость подтвердило это сообщение

К сожалению, в netcore пока нет CallContext, но enSO подсказало выход, который при тестировании вел себя аналогично и никаких проблем не выявил.

В итоге, как выглядит код:

using (ThrottleService.SetThrottler(new Throttler(15)))
{
  godObject.Download();
}

ThrottleService просто ставит в CallContext экземпляр троттлера, а GetPage внутри просто обращается к CallContext и проверяет, как там у него с ограничениями.

Отлично сработало с создаваемыми подтасками, внутри троттлера тупо SemaphoreSlim.

Из того, что ещё можно сделать - можно вкладывать троттлеры друг в друга, чтобы работали конструкции вида (созданные в разных методах):

using (ThrottleService.SetThrottler(new Throttler(15)))
using (ThrottleService.SetThrottler(new Throttler(10)))

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

Тут же кроется и минус - если ты ограничение указал в конкретном месте, то выше по стеку можно только ещё больше ограничить условия, передать только своё ограничение уже не получится. Над этим можно подумать и изобрести костыль, но в целом тут скорее прав A K и стоит подумать над рефакторингом. Пока, в целях ускорения решения задачи я скорее скомбинирую код - добавлю и решение с CallContext и добавлю возможность явно передавать троттлер, для случаев когда он и так под рукой имеется.

Answer 2

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

Ведь вы и сами понимаете положа руку на сердце, что пошли по кривой тропке, где ваш объект всё больше становится god object - и понимаете, что переделка сигнатуры метода уже дастся немалой кровью — но пока не готовы вернутся на правильную дорогу. Что ж, идите дальше — просто дальше цена будет лишь больше.

По мне вам просто нужно решиться на нормальный рефакторинг. Видел много людей, которые долго собираются с мыслью пойти к стоматологу, а потом удивляются, чего же это они раньше не пошли. Тут что-то похожее. Вам пока не поздно — отрефакторьте по-уму, самому же потом проще будет, особенно когда будете добавлять отдельные потоки/обработчики, каждый из которых будет свою прокси иметь, свои очереди и полиси.

Мне видится это как полноценный объект, который умеет принимать ссылку в очередь и умеет работать с политиками очереди. Ну и пашет себе где-то в отдельном от UI потоке.

READ ALSO
Пользовательский элемент из класса

Пользовательский элемент из класса

Есть тут люди которые помогут разобраться с созданием кастомных элементов для WPF C#?

163
Entity Framework работа с тяжелыми таблицами

Entity Framework работа с тяжелыми таблицами

У меня есть таблица городов около 25 миллионов записей (весит 2гб) и если в SQL запрос выполняется моментально по поиску, то Entity Framework начинает...

118
Получить ответ от сайта c помощью Cefsharp

Получить ответ от сайта c помощью Cefsharp

Добавил стандартное окно браузера от Cefsharp, пытаюсь авторизоваться на сайте и получить ответ при помощи IRequestHandler, но в итоге из за OnBeforeResourceLoad...

138