Подтвердить что ты не робот

Почему ASP.NET HttpContext.Current не задается при запуске задачи с текущим контекстом синхронизации

Я немного поиграл с асинхронными функциями .NET и придумал ситуацию, которую я не мог объяснить. При выполнении следующего кода внутри синхронного ASP.NET MVC-контроллера

var t = Task.Factory.StartNew(()=>{
        var ctx = System.Web.HttpContext.Current;
        //ctx == null here
},
    CancellationToken.None,
    TaskCreationOptions.None,
    TaskScheduler.FromCurrentSynchronizationContext()
);

t.Wait();

ctx null внутри делегата. Теперь, насколько я понимаю, контекст должен быть восстановлен при использовании планировщика задач TaskScheduler.FromCurrentSynchronizationContext(). Так почему же он не здесь? (Я могу, кстати, видеть, что делегат выполняется синхронно в одном потоке).

Кроме того, из msdn a TaskScheduler.FromCurrentSynchronizationContext() должен вести себя следующим образом:

Все экземпляры задач, помещенные в очередь для возвращаемого планировщика, будут выполнены через вызов метода Post в этом контексте.

Однако, когда я использую этот код:

var wh = new AutoResetEvent(false);

SynchronizationContext.Current.Post(s=> {
    var ctx = System.Web.HttpContext.Current;
    //ctx is set here
    wh.Set();
    return;
},null);

wh.WaitOne();

Фактически задан контекст.

Я знаю, что этот пример немного изобретателен, но мне очень хотелось бы понять, что происходит, чтобы увеличить понимание асинхронного программирования на .NET.

4b9b3361

Ответ 1

Ваши наблюдения кажутся правильными, это немного озадачивает. Вы указываете планировщик как "TaskScheduler.FromCurrentSynchronizationContext()". Это связывает новый "SynchronizationContextTaskScheduler". Теперь, если вы посмотрите на этот класс, он использует: enter image description here

Итак, если планировщик задач имеет доступ к той же "Синхронизации" Контекст ", который должен ссылаться" LegacyAspNetSychronizationContext". Так что, похоже, что HttpContext.current не должен быть нулевым.

Во втором случае, когда вы используете SychronizationContext (Refer: Статья MSDN), контекст потока делится с задачей:

"Еще одним аспектом SynchronizationContext является то, что каждый поток имеет" текущий "контекст. Контекст нитей не обязательно уникален; его контекстный экземпляр может использоваться совместно с другими потоками".


SynchronizationContext.Current предоставлен LegacyAspNetSychronizationContext в этом случае и внутренне имеет ссылку на HttpApplication.

Когда метод Post должен вызывать зарегистрированный обратный вызов, он вызывает HttpApplication.OnThreadEnter, что в конечном итоге приводит к установке текущего контекста потока как HttpCurrent.Context:

enter image description here

Все классы, на которые здесь ссылаются, определяются как внутренние в рамках и затрудняют дальнейшее исследование.

PS: Обозначая, что оба объекта SynchornizationContext на самом деле указывают на "LegacyAspNetSynchronizationContext": enter image description here

Ответ 2

Ваши результаты нечетны - вы уверены, что ничего не происходит?


Ваш первый пример (с Task) работает только потому, что Task.Wait() может запустить тело задачи "inline".

Если вы поставите точку останова в задаче лямбда и посмотрите на стек вызовов, вы увидите, что лямбда вызывается из метода Task.Wait() - нет concurrency. Поскольку задача выполняется только с обычными вызовами синхронного метода, HttpContext.Current должен возвращать то же значение, что и в любом другом месте вашего метода контроллера.


Второй пример (с SynchronizationContext.Post) будет заторможен, а ваш лямбда никогда не запустится.

Это потому, что вы используете AutoResetEvent, который ничего не знает о вашем Post. Вызов WaitOne() будет блокировать поток до тех пор, пока AutoResetEvent не будет Set. В то же время, SynchronizationContext ожидает, что поток будет свободен, чтобы запустить лямбда.

Поскольку поток заблокирован в WaitOne, опубликованная лямбда никогда не будет выполняться, что означает, что AutoResetEvent никогда не будет установлен, что означает, что WaitOne никогда не будет удовлетворен. Это тупик.

Ответ 3

Я уже давно искал информацию HTTPContext. И я нашел это:

http://odetocode.com/articles/112.aspx

Это о потоковом и HTTPContext. Существует хорошее объяснение:

CallContext предоставляет услугу, очень похожую на локальную память потоков (за исключением того, что CallContext может выполнять дополнительную магию во время удаленного вызова). Локальное хранилище потоков - это концепция, в которой каждый логический поток в домене приложения имеет уникальный слот данных для хранения данных, специфичных для себя. Темы не делят данные, и один поток не может изменять данные локально в другой поток. ASP.NET после выбора потока для выполнения входящего запроса хранит ссылку на текущий контекст запроса в локальном хранилище потоков. Теперь, независимо от того, где поток идет во время выполнения (бизнес-объект, объект доступа к данным), контекст находится поблизости и легко извлекается.

Зная вышеизложенное, мы можем указать следующее: если при обработке запроса выполнение переходит к другому потоку (через QueueUserWorkItem или асинхронный делегат в виде двух примеров), HttpContext.Current не будет знать, как получить текущий контекст и вернет null. Вы могли бы подумать, что одним из способов решения проблемы будет передача ссылки на рабочий поток

Итак, вам нужно создать ссылку на свой HTTPContext.Current через некоторую переменную, и эта переменная будет перенаправлена ​​из других потоков, которые вы создадите в своем коде.