Что такое контекст c
Каждая переменная доступна в рамках определенного контекста или области видимость. Вне этого контекста переменная уже не существует.
Существуют различные контексты:
Контекст класса. Переменные, определенные на уровне класса, доступны в любом методе этого класса. Их еще называют глобальными переменными или полями
Контекст метода. Переменные, определенные на уровне метода, являются локальными и доступны только в рамках данного метода. В других методах они недоступны
Контекст блока кода. Переменные, определенные на уровне блока кода, также являются локальными и доступны только в рамках данного блока. Вне своего блока кода они не доступны.
Например, пусть код программы определен следующим образом:
Здесь определенно четыре переменных: type, name, shortName и surname. Каждая из них существует в своем контексте. Переменная type существует в контексте всего класса Person и доступна в любом месте и блоке кода в методах PrintName и PrintSurname.
Переменная name существует только в рамках метода PrintName. Также как и переменная surname существует в рамках метода PrintSurname. В методе PrintName мы не можем обратиться к переменной surname , так как она в другом контексте.
Переменная shortName существует только в блоке кода, границами которого являются открывающая и закрывающая фигурные скобки. Вне его границ переменная shortName не существует и к ней нельзя обратиться.
Нередко границы различных контекстов можно ассоциировать с открывающимися и закрывающимися фигурными скобками, как в данном случае, которые задают пределы блока кода, метода, класса.
При работе с переменными надо учитывать, что локальные переменные, определенные в методе или в блоке кода, скрывают переменные уровня класса, если их имена совпадают:
При объявлении переменных также надо учитывать, что в одном контексте нельзя определить несколько переменных с одним и тем же именем.
What is a context?
It seems to me that a Context class is a control console whose object can invoke any included functions, such as Datacontext and DomainContext in WCF Ria service. Do I understand this concept correctly? If so, in what circumstances do I need to create a context class in my own class hierarchy?
Beside DataContext, what other well-known Context classes does the .net framework have?
5 Answers 5
You can think about the context as a wrapper for related «things» such as HttpContext, DbContext, ObjectContext. i.e.: HttpContext contains any information you can reach for HTTP related operations.
DbContext contains the methods and properties for database communication. Likewise ObjectContext.
I would say it’s a placeholder or container of related things for something.
![]()
To me, a context object defines a set of values and/or functions that are bound to the current execution path. In other words, just like speaking about a technical topic in the context of a job interview is different than speaking about the same topic at a nerd dinner, the context changes based on factors that affect the runtime environment of the consuming code. That seems abstract, but I can’t think of a better way to describe it at the moment!
Another famous context in .NET is the HttpContext object. Which values will change based on what Http operation is being handled. For example, the url will change in HttpContext.Current.Request.Uri . Hope that puts it in context for you 🙂
A context is commonly a storage mechanism for a group of actions. HttpContext , for example
Encapsulates all HTTP-specific information about an individual HTTP request.
For your WCF example, the «context» is the service. Different services have different contexts. Contexts can be as granular as you want. Some are broad, like the DomainContext , and some are granular, like HttpContext .
Contexts are everywhere, make them when you need to access or set like minded data or functions to things that can be decoupled.
All contexts are like this, they just encapsulate logic for particular action sets.
Here is another post describing the context design pattern.
A context is an ordered sequence of properties that define an environment for the objects resident inside it. Contexts get created during the activation process for objects that are configured to require certain automatic services, such as synchronization, transactions, just-in-time activation, security, and so on. Multiple objects can live inside a context.
A new object’s context is generally chosen based on meta-data attributes on the class. Some important types of context are:
ExecutionContext:
This is the parent context, all the other contexts are a part of it. It is the system that .NET features like Task use to capture and propagate context, but has no behavior of its own.
SecurityContext:
This is where we find any security information that would normally be confined to the current thread. If your code needs to run as a particular user, you may be impersonating that user, or ASP.NET may be doing impersonation for you. In that case, the impersonation is stored in the SecurityContext
CallContext:
This allows the programmer to store custom data that should be available for the lifetime of a logical thread. Although considered bad practice in a lot of situations, it can avoid excessive numbers of method parameters as various context is passed around the program. LogicalCallContext is a related system that works across AppDomains.
Изучаем net/context в Go
Не секрет, что основная ниша использования Go это сетевые сервисы: всевозможные серверы, бекенды, микросервисы, распределенные базы данных и файловые хранилища. Такой класс программ очень активно использует сетевые запросы, весь необходимый функционал для которых есть в стандартной библиотеке, но один аспект разработки сетевых архитектур остается для многих темным пятном — контексты запросов. В этой статье я хочу рассмотреть этот аспект повнимательней и показать, какой это мощный и важный инструмент.
Что такое контекст?
Начнём с главного вопроса — что это вообще за понятие такое, «контекст»? Контекст, в данном, простите за тавтологию, контексте — это некоторая информация об объекте, которая передается между границами функций API. Под объектом обычно подразумевается сетевой запрос, а границами API — различные middleware, разные пакаджи и слои абстракций, но само понятие «контекста» не специфично только лишь для сетевых запросов. Но в данной статье речь будет преимущественно о них.
Типичный пример — извлечь из JWT-токена данные о пользователе и передать дальше в обработчики эту информацию для проверки доступа, записи в лог и так далее.
Эта концепция контекста, как минимум для сетевых запросов, есть во многих языках и фреймворках — в C# это HttpContext, в Java-вовском Netty — ChannelHandlerContext, в питонвском Twisted — twisted.python.context и так далее.
Контексты в Go
В стандартной библиотеке Go есть отличный HTTP стек, который позволяет очень быстро создавать многопоточные сервера, не боясь «проблемы 10К соединений», и легко реализовывать самые разнообразные сценарии обработки запросов с помощью интерфейса Handler. Но понятия контекста для http-хендлеров в стандартной библиотеке нет.
Но у Go, помимо основной стандартной библиотеки, есть от авторов Go и отдельная группа пакетов, которые разрабатываются вне основного кода Go, и не имеют жесткого обещания обратной совместимости, как в стандартной библиотеке. Это все пакеты, которые лежат в golang/x/. Среди них достаточно давно есть пакет net/context, реализующий ту самую сущность контекстов и о котором мы сегодня и поговорим. На момент написания статьи, этот пакет уже используется, согласно GoDoc, в 1560 пакетах.
Отчасти отсутствие контекста в стандартной библиотеке было причиной появления целого зоопарка веб-фреймворков, каждый из которых решал вопрос передачи контекста запроса по-своему.
Зоопарк фреймворков
Используя слово «зоопарк» я немного утрирую, поскольку проблемы особой нет. Стандартный net/http хорош как фундамент, но как-только вы начинаете писать какой-нибудь веб-сервис, вы рано или поздно приходите к необходимости реализовывать более продвинутый функционал — сложный роутинг с группировкой, продвинутое логгирование, обработку авторизации и разграничения доступа и так далее, и это естественным образом превращается в некий фреймворк — многим наверняка такой же функционал будет нужен и такой же подход будет по душе. Но в целом, обычно есть пара наиболее популярных фреймворков, и они время от времени меняются, соревнуясь в zero-memory allocations и скорости. Сейчас, пальма первенства по популярности, вроде как у gin-gonic.
Каждый из фреймворков подходил к проблеме передачи контекстов по-своему. GorillaToolkit держит глобальный мап значений для каждого запроса, охраняя запросы к нему мьютексами. Goji и другие хранят отдельный мап для каждого запроса. gocraft/web работает с контекстами через reflection. У вышеупомянутого Gin — Context вообще ключевая структура, с которой работают все хендлеры. В echo на понятие Context вообще навесили все функции обработки запроса.
Каждый из этих подходов может иметь свои плюсы и минусы, но у всех у них один минус — привязка к фреймворку. Как уже упоминалось выше, контекст есть понятие достаточно абстрактное и не ограниченное http-запросами. Давайте разберем это на примерах.
Пример
Начнём с простого веб-сервиса:
Но тут мы захотели примитивную авторизацию по пин-коду, создадим простой middleware с помощью http.HandlerFunc:
Отлично, но теперь мы хотим пин читать из SQL-базы, а не хардкодить. Окей, пока что добавим глобальную переменную *sql.DB:
И дальше список наших требований и желаний начинает расти:
- а можно каждый запрос с неправильным пин-кодом логгировать в syslog?
- а можно ещё проверять и записывать IP адрес?
- а можно для разных IP делать шардинг и бегать в разные базы?
- а можно .
Вот тут и приходит на помощь понятие контекста. Каждая middleware-функция устанавливает свои значения в переменную контекста, и передает запрос и контекст дальше. В этот контекст вы вольны засовывать всё что угодно — статус авторизации, имя пользователя, разрешения доступа, информацию вытащенную из заголовков начального запроса, указатель на соединение с базой данных, уникальный ID для дальнейшего трекинга и так далее.
Обычно context.Context передается как параметр функции (как middleware, так и любой функции, работающий с запросом). В принципе, можно Context сохранять, как поле структуры, но это нужно делать осторожно, так как Context должен быть привязан к запросу — создаваться на каждый запрос и удаляться после завершения обработки запроса.
Но net/context это не только о хранении значений, это и унифицированный подход к управлению таймаутами и отменой запроса. Давайте посмотрим поподробнее.
net/context изнутри
API работы с net/context немного необычное, поэтому будьте готовы к удивлению вначале.
Тут важно понимать ещё, как обычно в Go создаются сложные пайплайны обработки запроса — обычно это горутина, принимающая один запрос, которая порождает одну или больше горутин, обрабатывающую этот запрос или поток запросов, которые, в свою очередь, возвращают результат наверх. Это может быть как простые синхронные вызовы функций, возможно, разнесенных в отдельные пакаджи, так и целые каскады новых горутин и каналов, передающих каналы. Главное то, что есть четкие границы ответственности каждого middleware, каждого пакаджа, и каждый из них что-то хочет знать о запросе, и что-то хочет над ним сделать.
Поэтому первый и главный принцип работы net/context заключается в том, что контексты являются вложенными и имеют древодвидную структуру. Собственно, основные функции пакета net/context и занимаются тем. что создают новые вариации контекста из уже существующего, порождают новый «подконтекст»:
Второй важный момент — это то, что context.Context является интерфейсом, и пакет net/context предоставляет лишь несколько вариаций, но вы вольны создавать свои контексты какой угодно сложности.
Давайте разберемя с основными видами существующих контекстов:
- context.Background() — это пустой контекст, у него нет значений, нет таймаута или возможности отмены; как правило Background() используется в функции, первой принимающей входящий запрос и является основой для всех последующих производных запросов.
Вот пример из camilstore: - context.TODO() — это специальный контекст, тоже пустой, но использующийся тогда, когда неясно какой контекст использовать, или функция ещё не была отрефакторена, чтобы принимать контекст. Имя TODO было выбрано специально, чтобы статические анализаторы кода могли легко находить этот случай. На данный момент, впрочем, линтера, который анализирует контексты еще нет в открытых исходниках, но есть в Google и будет открыт в будущем.
- context.WithCancel(parent Context) (ctx Context, cancel CancelFunc) — возвращает копию контекста parent с новым каналом Done и функцию CancelFunc, которая инициирует закрытие этого канала.
Давайте посмотрим, как это работает:
Если вы поняли, как работает WithCancel, то с этими двумя функциями проблем не должно быть. Пример выше упростится на пару строчек, и можно делать например вот так:
Чуть подробнее про context.WithValue
В отличие от фреймворков, упоминавшихся выше net/context не использует мапы или какие-либо подобные структуры данных по причине своей древовидной вложенной архитектуры. Один контекст несет одно значение плюс родительский контекст. Новое значение — это уже будет новый контекст. Как ключ так и само значение могут быть любого типа.
Стандартный механизм использования этого следующий — ключ должен быть неэкспортируемым типом, чтобы избежать коллизий с другими пакетами/API, которые могут работать с контекстом. Например:
Сам же контекст со значением выглядит следующим образом (https://github.com/golang/net/blob/master/context/context.go#L433):
Как видите, при любой вложенности контекста, Value() будет подниматься вверх по дереву контекста, пока не встретит нужное значение нужного типа. Ну, или вернет nil, поскольку Value() определен для всех контекстов (Context, как вы помните, это интерфейс, а, следовательно, определен и для Background-контекста тоже).
Полный код примера пакета, работающим со значениями контекста может быть примерно таким:
Что и как вы будете класть в контекст — зависит от конкретной задачи. Это может быть как простой ID пользователя, так сложная структура с массой информации внутри, так и объект вроде sql.DB для работы с базой данных.
Плюшки
Реализация контекста в виде универсального интерфейса позволяет дружить код, который использует контексты из других фреймворков с кодом, использующим net/context.
Вот пример контекста, использущего gorilla/context: blog.golang.org/context/gorilla/gorilla.go
А вот пример пример работы с отменой запроса в другом фреймворке, tomb: blog.golang.org/context/tomb/tomb.go
Или пакет net/trace, как пример использования контекста для трейсинга жизни запроса в стиле Dapper. Родительский контекст порождает context.WithValue(ctx, trace) и все последующие вызовы и контексты, которые будут порождены в процессе обработки запроса — будут содержать ID трейса, а уже код net/trace содержит нужные хендлеры, которые предоставляют информацию о трейсах на веб странице по пути /debug/requests и /debug/events.
Очевидно, что наибольшая выгода будет, если весь код, который общается с внешними ресурсами или порождает новые горутины, будет использовать net/context. К примеру, кодовая база Google на Go, которая насчитывает уже около 10+млн строк кода, везде использует context.Context. Новый RPC-фреймворк gRPC на Protobuf3, когда генерирует код для Go, также везде передает context.Context.
Планы на будущее
Вот тут идёт активное обсуждение будущих планов net/context, и, вполне вероятно, что context появится в стандартной библиотеке в Go 1.7. Возможно с небольшими изменениями, возможно без, но, в любом случае, интерес и желание есть, поэтому стоит держать руку на пульсе.
Стандартная библиотека, само собой, будет обратно совместима, и вещей вроде разделения на http и ctxhttp не будет, чтобы не фрагментировать кодовую базу (хотя пакет ctxhttp сейчас, в качестве эксперимента существует). Возможно в http.Request добавится поле Context, а возможно, придут к какому-нибудь ещё варианту.
Слово net из net/context, скорее всего пропадет.
Ссылки
Если хотите более подробно разобраться и посмотреть примеры, очень рекомендую следующие ссылки:
Структура CONTEXT
К этому моменту Вы должны понимать, какую важную роль играет структура CONTEXT в планировании потоков. Система сохраняет в ней состояние потока перед самым отключением его от процессора, благодаря чему его выполнение возобновляется с того места, где было прервано
Вы, наверное, удивитесь, но в документации Platform SDK структуре CONTEXT отведен буквально один абзац:
«В структуре CONTEXT хранятся данные о состоянии регистров с учетом специ фики конкретного процессора. Она используется системой для выполнения различ ных внутренних операций. В настоящее время такие структуры определены для про цессоров Intel, MIPS, Alpha и PowerPC. Соответствующие определения см. в заголовоч ном файле
В документации нет ни слова об элементах этой структуры, набор которых зави сит от типа процессора. Фактически CONTEXT — единственная из всех структур Windows, специфичнаядля конкретного процессора.
Так из чего же состоит структура CONTEXT Давайте посмотрим Ее элементы чет ко соответствуют регистрам процессора. Например, для процессоров x86 в число элементов входят Eax, Ebx, Ecx, Edx и т д., а для процессоров Alpha — IntVO, IntTO, IntT1, IntSO, IntRa, IntZero и др. Структура CONTEXT для процессоров x86 выглядит так.
typedef struct _CONTEXT <
// Флаги, управляющие содержимым записи CONTEXT.
// Если запись контекста используется как входной параметр, тогда раздел,
// управляемый флагом (когда он установлен), считается содержащим
// действительные значения, Если запись котекста используется для
// модификации контекста потока, то изменяются только те разделы,
// которых флаг установлен
// Если запись контекста используется как входной и выходной параметр
// для захвата контекста потока, возвращаются только те разделы контекста,
// для которых установлены соответствующие флаги. Запись контекста никогда
// не используется только как выходной параметр.
// Этот раздел определяется/возвращается, когда в ContextFlags установлен
// флаг CONTEXT_DEBUG_REGISTERS. Заметьте, что
// не включаются в CONTEXT_FUlL.
// Этот раздел определяется/возвращается, когда в ContextFlags
// установлен флаг CONTEXT_FLOATING_POINT,
// Этот раздел определяется/возвращается, когда в ContextFlags
// установлен флаг CONTEXT_SEGMENTS
// Этот раздел определяется/возвращается, когда в ContextFlags
// установлен флаг CONTEXT_INTEGER
// Этот раздел определяется/возвращается, когда в ContextFlags
// установлен флаг CONTEXT_CONTROL.
DWORD SegCs; // следует очистить
DWORD EFlags, // следует очистить
// Этот раздел определяется/возвращается, когда в ContextFlags
// установлен флаг CONTEXT_EXTENDED_REGISTERS
// Формат и смысл значений зависят от типа процессора.
Эта структура разбита на несколько разделов. Раздел CONTEXT_CONTROL содер жит управляющие регистры процессора: указатель команд, указатель стека, флаги и адрес возврата функции. (В отличис от x86, который при вызове функции помещает адрес возврата в стек, процессор Alpha сохраняет адрес возврата в одном из регист ров,) Раздел CONTEXT_INTEGER соответствует целочисленным регистрам процессо ра, CONTEXT_FLOATING_POINT — регистрам с плавающей точкой, CONTEXT_SEG
MENTS — сегментным регистрам (только для x86), CONTEXT_DEBUG_REGISTERS —
регистрам, предназначенным для отладки (только для x86), a CONTEXT_EXTEN DED_REGISTERS — дополнительным регистрам (только для x86 ).
Windows фактически позволяет заглянуть внутрь объекта ядра «поток» и получить сведения о текущем состоянии регистров процессора. Для этого предназначена функция:
BOOL GetThreadContext( HANDLE hThread, PCONTEXT pContext);
Создайте экземпляр структуры CONTEXT, инициализируйте нужные флаги (в эле менте ContextFlags) и передайте функции GetThreadContext адрес этой структуры. Функция поместит значения в элементы, сведения о которых Вы запросили
Прежде чем обращаться к GetThreadContext, приостяновите поток вызовом Sus pendThread, иначе поток может быть подключен к процессору, и значения регистров существенно изменятся. На самом деле у потока есть два контекстапользовательско го режима и режима ядра. GetThreadContext возвращает лишь первый из них. Если Вы вызываете SuspendThread, когда поток выполняет код операционной системы, пользо вательский контекст можно считать достоверным, даже несмотря на то что поток еще не остановлен (он всс равно не выполнит ни одной команды пользовательского кода до последующего возобновления)
Единственный элемент структуры CONTEXT, которому не соответствует какой либо регистр процессора, — ContextFlags. Присутствуя во всех вариантах этой струк туры независимо от типа процессора, он подсказывает функции GetThreadContext, значения каких регистров Вы хотите узyать. Например, чтобы получить значения управляющих регистров для потока, напишите что-то вроде:
// создаем экземпляр структуры
CONTEXT CONTEXT Context;
// сообщаем системе, что нас интересуют сведения
// только об управляющих регистрах
Context ContextFlags = CONTEXT_CONTROL;
// требуем от системы информацию о состоянии
// регистров процессора для данного потока
// действительные значения содержат элементы структуры CONTEXT,
// соответствующие управляющим регистрам, остальные значения
Перед вызовом GetThreadContext надо инициализировать элемент ContextFlags. Чтобы получить значения как управляющих, так и целочисленных регистров, иници ализируйте его так
// сообщаем системе, что нас интересуют
// управляющие и целочисленные регистры
Context.ContextFlags = CONTEXT_CONTROL | CONTEXT INTEGER;
Есть еще один идентификатор, позволяющий узнать значения важнейших регис тров (т. e. используемых, по мнению Microsoft, чаще всего):