Чем семантика AsyncLocal отличается от логического контекста вызова?
.NET 4.6 представляет AsyncLocal<T>
класс для передачи окружающих данных по асинхронному потоку управления. Я ранее использовал CallContext.LogicalGet/SetData
для этой цели, и мне интересно, если и как эти два семантически различаются (за исключением очевидных различий API, таких как строгая типизация и отсутствие зависимости от строковых ключей).
3 ответа
Семантика почти такая же. Оба хранятся в ExecutionContext
и поток через асинхронные вызовы.
Различия - это изменения API (как вы описали) вместе с возможностью регистрации обратного вызова для изменений значения.
Технически, есть большая разница в реализации, так как CallContext
клонируется каждый раз при копировании (используя CallContext.Clone
) в то время как AsyncLocal
данные хранятся в ExecutionContext._localValues
словарь и просто эта ссылка копируется без какой-либо дополнительной работы.
Чтобы обновления влияли только на текущий поток при изменении AsyncLocal
's value создается новый словарь, и все существующие значения копируются в новое.
Эта разница может быть как хорошей, так и плохой для производительности, в зависимости от того, где AsyncLocal
используется.
Теперь, как упоминал Ганс Пассант в комментариях CallContext
изначально был создан для удаленного взаимодействия и недоступен, если удаленное взаимодействие не поддерживается (например,.Net Core), поэтому, вероятно, AsyncLocal
был добавлен в рамки:
#if FEATURE_REMOTING
public LogicalCallContext.Reader LogicalCallContext
{
[SecurityCritical]
get { return new LogicalCallContext.Reader(IsNull ? null : m_ec.LogicalCallContext); }
}
public IllogicalCallContext.Reader IllogicalCallContext
{
[SecurityCritical]
get { return new IllogicalCallContext.Reader(IsNull ? null : m_ec.IllogicalCallContext); }
}
#endif
Примечание: есть также AsyncLocal
в Visual Studio SDK, который в основном является оберткой над CallContext
который показывает, насколько похожи понятия: Microsoft.VisualStudio.Threading.
Мне интересно, если и чем эти два семантически отличаются
Из того, что видно, оба CallContext
а также AsyncLocal
внутренне полагаться на ExecutionContext
хранить свои внутренние данные внутри Dictionary
, Последний, кажется, добавляет еще один уровень косвенности для асинхронных вызовов. CallContext
существует с.NET Remoting и был удобным способом передачи данных между асинхронными вызовами, где до сих пор не было реальной альтернативы.
Самая большая разница, которую я могу заметить, заключается в том, что AsyncLocal
теперь позволяет регистрироваться в уведомлениях с помощью обратного вызова, когда базовое сохраненное значение изменяется, либо ExecutionContext
переключить или явно, заменив существующее значение.
// AsyncLocal<T> also provides optional notifications
// when the value associated with the current thread
// changes, either because it was explicitly changed
// by setting the Value property, or implicitly changed
// when the thread encountered an "await" or other context transition.
// For example, we might want our
// current culture to be communicated to the OS as well:
static AsyncLocal<Culture> s_currentCulture = new AsyncLocal<Culture>(
args =>
{
NativeMethods.SetThreadCulture(args.CurrentValue.LCID);
});
Кроме этого, один проживает в System.Threading
в то время как другой живет в System.Runtime.Remoting
где первый будет поддерживаться в CoreCLR.
Кроме того, не похоже, что AsyncLocal
имеет мелкую семантику копирования при записи SetLogicalData
имеет, поэтому данные передаются между вызовами без копирования.
Кажется, есть некоторая семантическая разница во времени.
С CallContext изменение контекста происходит, когда задан контекст для дочернего метода thread/task/async, т.е. когда вызываются методы Task.Factory.StartNew(), Task.Run() или async.
С AsyncLocal изменение контекста (вызывается обратный вызов уведомления об изменении) происходит, когда фактически начинает выполняться дочерний метод thread/task/async.
Разница во времени может быть интересной, особенно если вы хотите, чтобы объект контекста был клонирован при переключении контекста. Использование разных механизмов может привести к клонированию разного контента: с помощью CallContext вы клонируете контент, когда создается дочерний поток / задача или вызывается асинхронный метод; но с помощью AsyncLocal вы клонируете содержимое, когда начинает выполняться метод дочернего потока / задачи / async, содержимое объекта контекста могло быть изменено родительским потоком.