.NET 4.6 вводит класс AsyncLocal<T>
для текущих окружающих данных по асинхронному потоку управления. Я ранее использовал CallContext.LogicalGet/SetData
для этой цели, и мне интересно, являются ли и в каком смысле два семантически различны (помимо очевидных различий API, таких как сильная типизация и отсутствие уверенности в строковых ключах).
Как семантика AsyncLocal отличается от контекста логического вызова?
Ответ 1
Семантика почти такая же. Оба сохраняются в ExecutionContext
и проходят через асинхронные вызовы.
Различия - это изменения API (как описано выше) вместе с возможностью регистрации обратного вызова для изменения значений.
Технически существует большая разница в реализации, поскольку CallContext
клонируется каждый раз при копировании (используя CallContext.Clone
), в то время как данные AsyncLocal
хранятся в словаре ExecutionContext._localValues
и только эта ссылка копируется без дополнительной работы.
Чтобы обновления обновлялись только при текущем потоке при изменении значения AsyncLocal
, создается новый словарь, а все существующие значения мелко копируются в новый.
Эта разница может быть хорошей и плохой для производительности, в зависимости от того, где используется 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
Примечание: в SDK Visual Studio также есть AsyncLocal
, который в основном представляет собой обертку поверх CallContext
, которая показывает, насколько похожи понятия: System.Threading.AsyncLocal.
Ответ 2
Мне интересно, если и каким образом эти два семантически отличаются
Из того, что видно, как 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
имеет неглубокую семантику copy-on-write SetLogicalData
, поэтому данные передаются между вызовами без копирования.