Использование HttpContext в Async Task
У меня есть следующее действие mvc.
public async Task<JsonResult> DoSomeLongRunningOperation()
{
return await Task.Run(() =>
{
//Do a lot of long running stuff
//The underlying framework uses the HttpContext.Current.User.Identity.Name so the user is passed on the messagebus.
}
}
В задаче HttpContext получает значение null. Мы много обманывали, но ничто не гарантирует нам, что HttpContext всегда доступен в нашем новом потоке.
Есть ли решение использовать HttpContext внутри асинхронных задач?
В нашем IocContainer мы зарегистрировали следующий объект, который передает имя пользователя в фреймворк.
public class HttpContextUserIdentityName : ICredentials
{
public string Name
{
get { return HttpContext.Current.User.Identity.Name; }
}
}
Этот код вызывается во многих местах до того, как он останется в базе данных.
Нам нужен либо другой способ получить имя пользователя пользователя, инициировавшего веб-запрос, либо исправить проблему, когда HttpContext имеет значение null.
Поскольку сохранение в базе данных происходит в Задаче, я не могу получить доступ к HttpContext перед входом в задачу.
Я также не могу придумать безопасный способ временного сохранения имени пользователя, чтобы я мог реализовать другой объект службы ICredentials.
Ответы
Ответ 1
Вы почти никогда не хотите использовать Task.Run
в методе ASP.NET.
Я думаю, что самым чистым решением (но самой работой) является реализация async
-совместимых интерфейсов на других уровнях:
public async Task<JsonResult> DoSomeLongRunningOperation()
{
//Do a lot of long running stuff
var intermediateResult = await DoLongRunningStuff();
return await DetermineFinalResult(intermediateResult);
}
Ответ 2
Я бы попытался передать ссылку на HttpContext как объект состояния, потому что это должно создать новый экземпляр этого объекта в стеке для потока, который выполняет эту работу. Вместо использования Task.Run используйте
return await Task.Factory.StartNew((ctx) =>
{
var context = (HttpContext)ctx;
//Do stuff
}, httpContextObject);
Task.Run и Task.Factory.StartNew возвращаются немедленно, поэтому asp.net продолжается в жизненном цикле события в рабочем потоке, который обрабатывает запрос, пока ваш поток работает с уже уложенным объектом.
Ответ 3
Перед тем, как начать новый поток, вы должны получить всю необходимую информацию из текущего контекста. В этом случае добавьте что-то вроде:
string username = HttpContext.Current.User.Username;
до Task.Run
, а затем используйте это внутри другого потока.
На стороне примечания, как она есть, нет причин для await
задачи. Вы можете просто вернуть задачу напрямую и не пометить метод как Async
.
Если вам нужно получить доступ к объекту Response
, который предположительно будет использовать результаты длительной операции и, следовательно, не может быть до Task.Run
, вы должны сделать это после Task.Run
(но убедитесь, что задача await
ed). Если вы это сделаете, вы не сможете сделать то, что я предложил в предыдущем абзаце.