HTTPContext через потоки - PullRequest
3 голосов
/ 31 марта 2012

Мне нужно создать экземпляр объекта-одиночки для каждого веб-запроса, чтобы данные обрабатывались один раз и действовали на протяжении всего запроса. Я использовал HttpContext.Current.Items для обмена данными во время HTTP-запроса, все было в порядке, пока нам не потребовался экземпляр объекта singleton в нескольких потоках, первое, что я придумал, - передать экземпляр HttpContext новому потоку:

HttpContext context = HttpContext.Current;
ThreadPool.QueueUserWorkItem(callback =>
    {
        HttpContext.Current = context;
        // blah blah
    });

Что я не считаю поточно-ориентированнымподход как отмеченный здесь .

Используя Reflector, я понял, что HttpContext.Current.Items фактически использует CallContext для хранения объектов в каждом логическом потоке.Поэтому я изменил одноэлементный интерфейс следующим образом:

public static SingletonType SingletonInstance
{
    get { return CallContext.GetData(key) as SingletonType; }
    set { CallContext.SetData(key, value); }
}

И просто перезаписать SingletonInstance при запуске любого нового потока!Код работает нормально, однако кажется, что каким-то образом при большой нагрузке CallContext.GetData (key) возвращает значение null, и приложение завершает работу с исключением с нулевой ссылкой!

Я думал, если CallContext.GetData является атомарным?Но это не кажется правильным, CallContext - это хранилище данных, специфичное для потока, и должно быть атомарным, или я упускаю суть! * Мое другое предположение состоит в том, что установка SingletonInstance (CallContext.SetData) происходит в одном потокев то время как CallContext.GetData выполняется в другом как , отмеченное здесь , но я не знаю как / почему?

update:

Мы сохраняемэкземпляр каждого онлайн-пользователя в массиве на сервере.Объект-одиночка на самом деле является ссылкой на объект, представляющий текущего пользователя.Текущий пользователь должен быть уникальным и доступным в каждом потоке для запросов к базе данных, ведения журнала, обработки ошибок и многого другого, вот как это делается:

public static ApplicationUser CurrentUser
{
    get { return CallContext.GetData("ApplicationUser") as ApplicationUser ; }
    set { CallContext.SetData("ApplicationUser", value); }
}

Ответы [ 3 ]

3 голосов
/ 31 марта 2012

ASP.NET может переносить запрос между потоками, если он находится под нагрузкой. После получения запроса конструктор страницы может выполняться в одном потоке, а загрузка страницы - в другом. В этом потоке переключатели CallContext и ThreadStatic не переносятся, но, к счастью, HttpContext.

Это может вводить в заблуждение, поскольку HttpContext является контекстом вызова, но это немного странно в ASP.NET, возможно, из-за сокращения углов для повышения производительности.

Вам нужно будет удалить зависимости от CallContext и использовать HttpContext на протяжении всего пути.

Вы можете прочитать более подробную информацию в этом потрясающем сообщении в блоге от Piers7.

1 голос
/ 31 марта 2012

Это было решено во время сеанса чата.

По сути, это включает в себя долгосрочные задачи, и предложение об использовании внешней службы (веб-службы или обычной службы Windows) было решено в качестве наилучшего решения проблемы.

0 голосов
/ 03 декабря 2015

Потокобезопасность вашего второго метода - лучший подход. Это потокобезопасная версия вашего синглета:

public sealed class SingletonType
{
    #region thread-safe singletone

    private static object _lock = new object();
    private SingletonType() { }

    public static SingletonType SingletonInstance
    {
        get
        {
            if (CallContext.GetData(key) == null)
            {
                lock (_lock)
                {
                    if (CallContext.GetData(key) == null)
                        CallContext.SetData(key, new SingletonType());
                }
            }

            return CallContext.GetData(key) as SingletonType;
        }
    }

    #endregion

    //
    //
    // SingletoneType members
    //
    //
}

ПРИМЕЧАНИЕ: ключом является lock { }.

...