Pull to refresh

Comments 13

Если для работы каких-то action-методов требуется, чтобы база заведомо содержала данные текущего пользователя, этот подход не годится. Придётся использовать явный вызов DbRefresher.RefreshAsync в теле метода.

А еще, если я не ошибаюсь, ждать окончания выполнения этого метода (в этом же или другом потоке, не суть).
Итого, можно лЁгко сделать метод синхронным и вызывать его в другом потоке только тогда, когда это реально надо.
CatchPrimaryKeyViolationAsync я бы перенес в DbRefresher.RefreshAsync, а оттуда выбрасывал бы что-то более значимое для доменной области. Например, DuplicatedUserException.
Я исхожу из предположения, что контроллеры у нас тоже асинхронные. В этом случае синхронные версии методов не нужны.

а оттуда выбрасывал бы что-то более значимое для доменной области. Например, DuplicatedUserException.

Это исключение не нужно, оно не имеет смысла. Если произошло primary key violation, значит, в данный момент другой поток выполняет работу по обновлению базы. Он её и закончит.

Постарался объяснить это более подробно в статье, спасибо за комментарий.
Если обновление в БД происходит по первичному ключу, а, исходя из исключения, это именно так, я бы вообще не делал асинхронный метод.
Если вам важен именно primary key violation в контроллере, то это протаскивание DAL на уровень, где о нем вряд ли должно быть что-то известно.
Если обновление в БД происходит по первичному ключу, а, исходя из исключения, это именно так, я бы вообще не делал асинхронный метод.

Почему?
Если вам важен именно primary key violation в контроллере, то это протаскивание DAL на уровень, где о нем вряд ли должно быть что-то известно.

Не совсем понял, что вы имели в виду.
Если обновление данных пользователя в БД отнимает слишком много времени — то вынесение этого обновления в фон может легко привести к ситуации, когда СУБД перегружена одними обновлениями.

Не лучше ли держать LRU-кеш последних авторизовавшихся пользователей, и обновлять данные в БД только когда данные пользователя из токена не совпали с данными из кеша?

Или же можно вовсе вытащить сам токен из свойства BootstrapContext, и положить в кеш его.
Мысль интересная, буду держать её в голове.
Тем более, что сейчас алгоритм обновления выглядит следующим образом:

  1. Обновляем компанию пользователя
    • Получаем компанию из БД
    • Если такой компании нет, добавляем новую компанию в БД
    • Если компания изменилась, обновляем компанию в БД

  2. То же самое для пользователя

Пока пользователей у нас немного, поэтому проблем с загрузкой СУБД нет. Если появятся – подумаем над вашим предложением.

Но должен отметить, что если используется вышеописанный алгоритм, загрузка СУБД будет одинаковой вне зависимости от того, выполняется обновление в фоне или в самом запросе.
Но должен отметить, что если используется вышеописанный алгоритм, загрузка СУБД будет одинаковой вне зависимости от того, выполняется обновление в фоне или в самом запросе.

Для типового клиента частота запросов к API будет зависеть от времени выполнения запроса. Чем быстрее запрос — тем быстрее придёт следующий.


Если же перед API поставить какое-нибудь балансировщик или ограничитель запросов — то он, опять-таки, будет измерять загруженность исходя из времени ответа на запрос, а висящие в фоне "хвосты" запросов учитываться не будут.

Согласен, не всё так просто.
Но если СУБД перегружена, стоит, видимо, задуматься о системном решении этой проблемы в первую очередь?
If the database is the bottleneck, asynchronous calls will not speed up the database response.

Вот я и предлагаю просто не делать лишних запросов к базе.
Если не ошибаюсь то async метод выполняется синхронно до первой await инструкции в его теле. Я бы посоветовал обернуть вызов DbRefresher.RefreshAsync в Task.Run.
Дельное замечание. Но вот какая штука: я специально расставил трассировки и убедился в том, что в случае Task.Run создаётся один поток до первого await и другой после.
Вопрос в том, стоит ли плодить лишние потоки?

На всякий случай, я пробовал такой код:
            if (IsAuthorized(actionContext))
                Task.Run(
                        () => DbRefresher.RefreshAsync(actionContext.RequestContext.Principal))
                    .ContinueWith(t =>
                    {
                        LogFactory.For<AuthorizeAndRefreshUserAttribute>()
                            .ErrorException("Error occured", t.Exception);
                    }, TaskContinuationOptions.OnlyOnFaulted);
Если до первой await инструкции не делается ничего тяжолого я с вами согласен.
Тот поток, который «другой после», не создаётся — а берётся из пула.
Sign up to leave a comment.

Articles