Pull to refresh

Comments 18

я нашел определенное количество тематических статей, но практически все они были ориентированы на использование Entity Framework как хранилища пользовательских данных.

EE — это действительно проблема. В одном проекте была построена инфраструктура на основе NHibernate — я так и не осилил «красивое» внедрение ASP.NET Identity. Пришлось костыли использовать.
«Красивое» — это в смысле такое же как в примерах по EF?
По-моему, можно вполне себе красиво реализовать свой UserStore, как например вот здесь, используя еще один важный класс DbContext, который, к сожалению не вошел в мой стартовый туториал.
Хм, реализация своего UserStore выглядит здорово. Скоро буду переделывать как раз таки этот участок проекта, попробую заново внедрить ASP.NET Identity. Спасибо за ссылку.
так сказать в копилку, реализация доп. провайдеров типа LinkedIn, Yahoo, GitHub… www.nuget.org/packages/Owin.Security.Providers/
только обратите внимание: LinkedIn недавно стал требовать обязательного указания Redirect_Url, раньше они это поле игнорировали.
Возможно, кто-нибудь сталкивался со следующей проблемой и знает как её решить:

Итак, в проекте используется связка EF + ASP.NET Identity. Как выше было уже отмечено, во главу угла в ASP.NET Identity поставлена асинхронность, т.е. почти каждый метод в этом фреймворке(каркасе?) возвращает объект типа Task. А это значит, что запрос начавшись в одном потоке, возможно закончит свое выполнение в другом.
Также известно, что EF не является потокобезопасным, более того, как только какой-нибудь поток пытается получить данные из контекста созданного в другом потоке, EF немедленно генерирует NotSupportedException с примерно следующим описанием:

«A second operation started on this context before a previous asynchronous operation completed. Use 'await' to ensure that any asynchronous operations have completed before calling another method on this context. Any instance members are not guaranteed to be thread safe.»

Вот здесь и здесь можно найти подробное обсуждение этой проблемы. В общем народ спихивает ответственность на AspNetSynchronizationContext.
Это звучит разумно и логично, однако мне не ясно следующее. Как работать с ASP.NET Identity + EF если у него есть такая проблема(например невозможно сделать два await вызова к одному контексту)?

Есть вероятность, что я упускаю, какое-то соверешенно очевидно решение, так как я не верю что парни из MS не думали\сталкивались с этой проблемой.
Это звучит разумно и логично, однако мне не ясно следующее. Как работать с ASP.NET Identity + EF если у него есть такая проблема(например невозможно сделать два await вызова к одному контексту)?


Сделать два разных контекста? Вообще хорошая практика DbContext per Request, с подсовыванием с помощью IoC.
В этом случае надо разделять понятия веб-запрос и запрос данных к контексту.

Представим такую ситуацию, к нам приходит запрос(веб-запрос) от клиента. Наш замечательный DI-контейнер создает экзепляр класса DbContext. После этого нам надо выполнить два действия:
1. Получить информацию о клиенте(например авторизационную информацию)
2. Получить какие-нибудь ещё данные специфичные для запроса

Таким образом к нашему объекту класса DbContext, нужно будет сделать два запроса данных. И тут появляется проблема, если эти запросы выполняются не одним и тем же потоком.

Есть другой вариант, создавать объект класса DbContext на каждый запрос данных. Я не проводил замеры производительности, но интуитивно чувствую, что этот подход будет иметь сильное негативное влияние на быстродействие.
Только что написал пример:
        public async Task<ActionResult> Index()
        {

            var dbContext = new TestEntites();
            var something = await dbContext.Foo.FirstOrDefaultAsync(e => e.Id == 1);
            var morething = await dbContext.Foo.FirstOrDefaultAsync(e => e.Id == 2);

            return View();
        }

Естественно работает без ошибок.
А вы не могли бы проверить, сколько потоков выполняют ваш код?

Например таким образом:
        public async Task<JsonResult> Index()
        {
            int initialThreadId = System.Threading.Thread.CurrentThread.ManagedThreadId;
            var dbContext = new TestEntites();

            var something = await dbContext.Foo.FirstOrDefaultAsync(e => e.Id == 1);
            int afterFirstAwaitThreadId = System.Threading.Thread.CurrentThread.ManagedThreadId;

            var morething = await dbContext.Foo.FirstOrDefaultAsync(e => e.Id == 2);
            int afterSecondAwaitThreadId = System.Threading.Thread.CurrentThread.ManagedThreadId;

            return Json(new { initial  = initialThreadId, first = afterFirstAwaitThreadId, second = afterSecondAwaitThreadId });
        }


Вполне может такое случится, что ваш запрос выполняет один и тот же поток, особенно если вы запускали код с прикрепленным отладчиком.
Поток один, как с отладчиком, так и без. При этом в callstack видно что это честное асинхронное продолжение
Ты думаешь что для такого кейса не запилили бы стабильную реализацию?
Я уверен, что её либо не запили вообще, либо это сделали не полностью. Это легко проверить, ASP.NET для создания и выполнения Task'ок использует AspNetSynchronizationContext, который в свою очередь использует ThreadPool для управления потоками.
Можно попробовать создать много запросов к приложению, и ждать когда продолжение(continuation) веб-запроса будет выполняться не потоком инициатором запроса. И вот тогда будет проблема(я с ней столкнулся). Опять-таки это проблема EF, а не проблема ASP.NET, так как EF не потокобезопасен.
Если интересно вникнуть чуть глубже, рекомендую посмотреть эти два вопроса и особенно ответы к ним на SO:
1. stackoverflow.com/questions/20946677/ef-data-context-async-await-multithreading
2. stackoverflow.com/questions/20993007/how-to-use-non-thread-safe-async-await-apis-and-patterns-with-asp-net-web-api

Во всей этой истории меня удивляет следующее, почему авторы ASP.NET Identity не предоставили синхронного API? Я понимаю, что проблем не должно быть со всякими MongoDB, потому как они асинхронны. Я понимаю, что проблемы нет в WPF и WinForms, так как они используют другой синхронизационный контекст. НО ё-маё, проблема есть в довольно часто используемой связке ASP.NET MVC + EF, когда сюда добавляем ASP.NET Identity

Я уверен, что её либо не запили вообще, либо это сделали не полностью.

:) Откуда такая уверенность?

Это легко проверить, ASP.NET для создания и выполнения Task'ок использует AspNetSynchronizationContext, который в свою очередь использует ThreadPool для управления потоками.
Можно попробовать создать много запросов к приложению, и ждать когда продолжение(continuation) веб-запроса будет выполняться не потоком инициатором запроса. И вот тогда будет проблема(я с ней столкнулся). Опять-таки это проблема EF, а не проблема ASP.NET, так как EF не потокобезопасен.

Запустил LoadTest, 500 запросов в секунду, ошибок нет. Так что нет проблем у EF и ASP.NET (в том числе MVC и WebAPI), по крайней мере тех, о которых вы думаете.
Проблема есть не только у меня, о чем свидетельствует SO.
Я постараюсь сегодня или завтра написать пример стабильно воспроизодящий проблему.
Проблема есть не только у меня, о чем свидетельствует SO.

не читайте до обеда советских газет

Пример будет интересен, только пожалуйста без экзотики.
Прошу прощения за задержку, пока не смог его дистиллировать из своего проекта, как только сделаю это обязательно отпишусь в эту ветку.
Кстати, есть замечательный цикл статей, который описывает структуру и архитектуру ASP.NET Identity, рекомендую ознакомится тем, кто только начинает работу с этим framework'ом. Плюс, как бонус, даются ссылки на реализации бибилотек с различными хранилищами, такими как RavenDB, MongoDB и т.д.
Sign up to leave a comment.

Articles