Почему 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.
Ответы
Ответ 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 через некоторую переменную, и эта переменная будет перенаправлена из других потоков, которые вы создадите в своем коде.