Зачем нужен контекст синхронизации на бекенде?
Как я понял польза от контекста синхронизации в том что он может позволить выполнить продолжение на UI потоке (иначе не сможем обновить UI, а если попробуем то будет исключение).
1. Верно понимание?
Я пробовал делать пример в консольном приложении .NET Core 2.2. Создал await c продолжительной асинхронной операцией, на продолжении будет использован тот же контекст что и до await. Учитывая те причины существования контекста, которые я знаю, то предполагаю что поток должен быть тот же как и до await. Но вывод на консоль показывал на продолжении не тот ID потока что был до await.
Как я понял из-за того что тут(.NET Core консоль) нет вообще контекста синхронизации при выполнении. Действительно, тут же нет UI, какая от него польза. Предполагаю что будет работать так как думал только на UI проектах типа UWP и WPF.
Но тут:
Я слышал, что в .NET Core нет необходимости применять ConfigureAwait (false). Так ли это?
есть информация что .NET Framework имеет свой контекст в не UI проектах + могут быть созданы кастомные контексты синхронизации в кор проектах.
2. Зачем вообще может быть полезно создавать/использовать не в UI проектах контекст?
3. Зачем в .NET Framework по умолчанию есть контекст синхронизации?
- Не уверен что правильно понял аргументы в пользу применения ConfigureAwait(false) на беке .NET Core. Правильно ли я понял что тут может быть смысл только если: (1)выделил часть логики, которую использую на беке, в либу и переиспользую в UI проекте.(2) опасаюсь сторонних либ которые могут привнести свой контекст и каким-то образом насадить на основной проект. Хотя зачем такие либы нужны на бекеке тоже вопрос. ?
Ответы (2 шт):
Старый ASP.NET имеет множество глобально доступных объектов, основной из которых - HttpContext.Current. Кстати, ASP.NET Core для тех же целей имеет интерфейс IHttpContextAccessor, но используется он не настолько широко: сейчас вполне возможно написать сложное веб-приложение ни разу не воспользовавшись этим интерфейсом - в то время как раньше прямо или косвенно HttpContext.Current использовался всегда и при выполнении любого запроса.
Изначально HttpContext.Current был просто ThreadStatic-полем, но когда в ASP.NET стали добавлять асинхронность - оказалось что значения ThreadStatic-полей после исполнения асинхронных операций "теряются". Но если IHttpContextAccessor решает эту проблему используя механизм AsyncLocal - то создатели ASP.NET так поступить не могли, потому что ASP.NET древнее этого механизма. Здесь-то им и пришла в голову идея использования контекста синхронизации.
По сути, главный смысл использования SynchronizationContext в ASP.NET - это восстановление HttpContext.Current после завершения асинхронной операции.
Заодно контекст синхронизации решает следующие задачи:
сериализация продолжений - все асинхронные продолжения выстраиваются в очередь, чтобы избежать возможных гонок при доступе к разделяемым объектам - в новом ASP.NET Core эту задачу возложили на программиста и частично на оператор await;
учёт асинхронных операций, чтобы знать когда обработка запроса закончилась - в новом ASP.NET Core вместо этого используются задачи (Task и ValueTask).
PS ссылка реализацию контекста синхронизации - https://referencesource.microsoft.com/#system.web/AspNetSynchronizationContext.cs
Контекст синхронизации это механизм, который позволяет решать две похожие задачи. Он позволяет выполнить асинхронный код в том же потоке, который сделал асинхронный вызов или он позволяет восстановить статические значения, привязанные к потоку, если продолжение запустилось в другом потоке.
Первый случай касается UI, а второй относится к бекенду. Теоретически, вы можете создать свой контекст синхронизации и реализовать свою логику, но я не слышал о практических реализациях такого рода.
Зачем контекст синхронизации в UI? Оконная подсистема Windows проектировалась во времена однопроцессорных систем и хорошо работала в однопоточной среде. В многопоточной она тоже работает, но с существенными ограничениями. Вы можете вызывать функцию SendMessage из разных потоков, но она работает синхронно. Если в это время выполняется SendMessage из другого потока, ваш поток будет ждать.
Асинхронные задачи призваны повысить утилизацию процессора, но это происходит только если они очень быстро завершаются. Ожидание SendMessage может затянуться, поэтому создатели Windows просто решили, что вызывать её могут только задачи из потока UI. Поток UI в программе одни, поэтому все таких вызовы выполняются последовательно и не мешают друг другу.
SendMessage используется для изменению и получения значений всех свойств всех элементов управления в Windows. Такая вот God-функция.
Если мы хотим что-то поменять, например текст лейбла или цвет поля ввода, мы обязаны сделать это в главном потоке, и в этом нам помогает контекст синхронизации UI-приложений.
Зачем контекст синхронизации в бекенде? В соседнем ответе уже осветили эту тему, но я отвечу по-своему, как я понимаю.
Исторически приложения ASP.NET были просто многопоточными. На каждый Web-запрос сервер создавал поток. Потоки от разных пользователей не перемешивались друг с другом, так что важные данные из запроса удобно было хранить в статических переменных потока.
В .NET есть такое понятие, поддерживаемое платформой, про которое не все уже помнят, потому что оно отмирает из-за повсеместного DI.
Что такое важные данные из запроса? Во-первых это пользователь, от лица которого выполняется запрос; и во-вторых — культура пользователя.
Если вам нужна защита приложения, вы реализуете аутентификацию: Basic, Digest, Windows Integrated или Bearer. Информация о текущем пользователе приходит в заголовке Authorization. ASP.NET извлекает эту информацию и сохраняет в свойстве HttpContext.User.
Благодаря этому код авторизации становится проще, вы можете отображать имя пользователя или сделать страницу профиля.
Культура пользователя извлекается из заголовка Accepted-Languages. Если там приходит ru-RU, сайт понимает, что отдавать информацию нужно на русском языке.
Вся эта конструкция прекрасно работала в многопоточной среде, но перестала работать в асинхронной. Продолжение вашего кода может быть выполнено в другом потоке.
А это значит, что после await у вас внезапно может поменяться пользователь и его язык.
Чтобы избежать такой ситуации, создатели ASP.NET реализовали контекст синхронизации для веба, который перед запуском продолжения устанавливает корректные значения в полях HttpContext.Current.
Однако этот процесс, выполняемый на каждый асинхронный вызов, отнимает много времени. Большая часть кода не использует культуру или текущего пользователя, так что её можно запускать на любом свободном потоке без всяких прелюдий. Как мы помним, для этого надо вызывать ConfigureAwait(false).
Только обычные программисты не заморачиваются этим вызовом, так что контекст синхронизации всё-равно постоянно используется.
Я думаю, именно поэтому в .NET Core от контекста синхронизации отказались совсем. ConfigureAwait(false) для веба не имеет смысла.
Экземпляр HttpContext доступен как свойство контроллера, так что вам не нужно больше обращение HttpContext.Current.