2016-09-26 7 views
8

У меня есть следующий пример кода, который используется в приложении ASP.NET MVC. Целью этого кода является создание запроса «огонь и забухание» для очередей в течение долгого времени.ASP.NET HttpContext.Current внутри Task.Run

public JsonResult SomeAction() { 
    HttpContext ctx = HttpContext.Current;    

    Task.Run(() => { 
     HttpContext.Current = ctx; 
     //Other long running code here. 
    }); 

    return Json("{ 'status': 'Work Queued' }"); 
} 

Я знаю, что это не лучший способ для обработки HttpContext.Current в асинхронном коде, но в настоящее время наша реализация не позволяет нам делать что-то другое. Я хотел бы понять, насколько этот код опасен ...

Вопрос: ли теоретически возможно, что установка HttpContext внутри Task.Run, установит контекст совершенно другой запрос?

Я думаю, да, но я не уверен. Как я понимаю: Request1 обрабатывается с Thread1 из пула потоков, тогда, когда Thread1 обрабатывает абсолютный другой запрос (Request2), код внутри Task.Run устанавливает контекст из Request1 в Request2.

Возможно, я ошибаюсь, но мои знания об элементах ASP.NET не позволяют мне правильно его понять.

Спасибо!

+3

Не проще ли просто получить информацию из HttpContext, которая вам нужна, а не передать весь HttpContext? (Я понимаю, что это не отвечает на ваш вопрос, но мне любопытно, что нужен весь контекст). – vcsjones

+2

Правильно, это очень правильный путь, но, к сожалению, в настоящее время я не могу его изменить. Наш код имеет доступ к HttpContext.Current глубоко внутри бизнес-логики и его изменение - это огромные усилия, которые в настоящее время у нас нет. –

+1

Не связанный с вашим вопросом - работа с длительным сроком работы в веб-контексте - отличная идея - сервер может быть перезапущен, и в пуле только столько потоков, как только вы закончите нить, вы перестанете обслуживать запросы. Вы считали что-то вроде HangFire или Quartz? –

ответ

5

Позвольте мне поднять немного внутренностей на вас:

public static HttpContext Current 
{ 
    get { return ContextBase.Current as HttpContext; } 
    set { ContextBase.Current = value; } 
} 

internal class ContextBase 
{ 
    internal static object Current 
    { 
     get { return CallContext.HostContext; } 
     set { CallContext.HostContext = value; } 
    } 
} 

public static object HostContext 
{ 
    get 
    { 
     var executionContextReader = Thread.CurrentThread.GetExecutionContextReader(); 
     object hostContext = executionContextReader.IllogicalCallContext.HostContext; 
     if (hostContext == null) 
     { 
      hostContext = executionContextReader.LogicalCallContext.HostContext; 
     } 
     return hostContext; 
    } 
    set 
    { 
     var mutableExecutionContext = Thread.CurrentThread.GetMutableExecutionContext(); 
     if (value is ILogicalThreadAffinative) 
     { 
      mutableExecutionContext.IllogicalCallContext.HostContext = null; 
      mutableExecutionContext.LogicalCallContext.HostContext = value; 
      return; 
     } 
     mutableExecutionContext.IllogicalCallContext.HostContext = value; 
     mutableExecutionContext.LogicalCallContext.HostContext = null; 
    } 
} 

Так

var context = HttpContext.Current; 

равна (псевдокод)

var context = CurrentThread.HttpContext; 

и внутри вашей Task.Run что-то подобное происходит

CurrentThread.HttpContext= context; 

Task.Run начнет новую задачу с потока из пула потоков. Итак, вы говорите, что ваш новый поток «Свойство HttpContext» относится к стартовому потоку «Свойство HttpContext» - пока что так хорошо (ну со всеми исключениями NullReference/Dispose, с которыми вы столкнетесь после завершения начального потока).Проблема заключается в том, если внутри

//Other long running code here. 

У вас есть заявление, как

var foo = await Bar(); 

После того, как вы нажмете ждать, ваш текущий поток возвращается в пул потоков, и после завершения ввода-вывода вы захватить новый поток из пула потоков - чудо каково его свойство «HttpContext», правильно? Я не знаю :) Скорее всего, вы закончите с NullReferenceException.

+0

Благодарим за подробный ответ! Из того, что вы написали, я понимаю, что технически это может случиться (Request2 получит контекст Request1), но, скорее всего, я получу нулевые ссылки и избавлюсь от исключений, потому что фактически Request1 завершен, и ASP.NET «очистил» его контекст. .. Я понимаю ваш ответ правильно? Благодаря! –

2

Проблема, с которой вы столкнетесь здесь, заключается в том, что HttpContext будет удален, когда запрос будет завершен. Поскольку вы не ожидаете результата Task.Run, вы по существу создаете условие гонки между удалением HttpContext и его использованием внутри задачи.

Я уверен, что единственной проблемой, с которой вы столкнетесь, является исключение NullReferenceException или исключение ObjectDisposedException. Я не вижу никакого способа, которым вы могли бы случайно украсть контекст другого запроса.

Кроме того, если вы не управляете & исключениями в вашей задаче, ваш огонь и забьют, и вы никогда не узнаете об этом.

Ознакомьтесь с HangFire или рассмотрите возможность использования очереди сообщений для обработки исходных заданий из отдельного процесса.

+0

Спасибо за ответ. Я знаю о состоянии гонки и возможном исключении NullReference/Disposed. Но я хотел бы технически понять, почему вы думаете, что этот способ не может украсть другой контекст запроса ... как я знаю, технически HttpContext.Current управляет контекстами по идентификатору потока, поэтому, если два запроса будут получать Thread с тем же идентификатором, то HttpContext.Current может вернуть ссылку на тот же объект из двух запросов. –