Чем семантика 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, содержимое объекта контекста могло быть изменено родительским потоком.

Другие вопросы по тегам