Использование [ThreadStatic] противоречит асинхронному коду?
У нас есть довольно большая существующая кодовая база для различных веб-сервисов, построенная на базе ASP.NET, и этот код интенсивно использует доступ HttpContext.Current.User
(завернутый как Client.User
Я уверен, что внутренне использует [ThreadStatic]
чтобы дать вам это окружение.
В настоящее время я изучаю, возможно ли, что мы начнем использовать больше асинхронного кода в форме async/await
но мне трудно найти, как использовать [ThreadStatic]
вписывается в это. Снятие зависимости от [ThreadStatic]
не очень возможно из-за его интенсивного использования.
Я понимаю, что когда await
ударил, выполнение кода останавливается там, вызов немедленно возвращается, и что продолжение установлено, чтобы продолжить выполнение, когда асинхронный код возвращается. Между тем, оригинальный поток может быть использован для чего-то другого, например, для обработки другого запроса. Пока мое понимание этого.
На самом деле я не могу найти однозначного ответа, является или нет HttpContext.Current.User
гарантированно будет одинаковым до и после await
,
Итак, в основном:
HttpContext.Current.User = new MyPrincipal();
var user = HttpContext.Current.User;
await Task.Delay(30000);
// Meanwhile, while we wait for that lots of other requests are being handled,
// possibly by this thread.
Debug.Assert(object.ReferenceEquals(HttpContext.Current.User, user));
В том, что Debug.Assert
гарантированно добиться успеха?
Если другой запрос был обработан тем же потоком, что и Task.Delay
ожидал, что запрос будет установлен другой HttpContext.Current.User
Так что, предыдущее состояние каким-то образом сохраняется и восстанавливается при вызове продолжения?
Что я могу представить, так это то, что за кулисами [ThreadStatic]
состояние сохраняется как своего рода словарь в самом потоке, и это, когда поток возвращается в пул потоков после возврата в этот поток await
этот словарь хранится где-то в безопасности и устанавливается обратно в поток, когда он выполняет продолжение (или в потоке, я не уверен, что это даже тот же поток, который обрабатывает продолжение), вероятно, с ободряющим похлопыванием по заднице и "иди, возьми их, мальчик!", но эта последняя часть может быть просто моим воображением.
Это несколько точно?
ОБНОВЛЕНИЕ: я попытался собрать небольшой тест, который пытается это. Пока что это работает, и утверждение не провалилось ни для одного из сотен запросов. Кто-нибудь может проверить, имеет ли смысл тест?
3 ответа
async
/await
являются независимыми от потоков, что означает, что они имеют соглашение, которое может работать в нескольких различных системах потоков.
В общем ThreadStatic
не будет работать правильно в async
/await
, за исключением тривиальных случаев, таких как контексты пользовательского интерфейса, где await
возобновит в потоке пользовательского интерфейса. Для ASP.NET ThreadStatic
не совместим с async
,
Тем не мение, HttpContext.Current
это особый случай. ASP.NET определяет "контекст запроса" (представленный AspNetSynchronizationContext
экземпляр назначен SynchronizationContext.Current
). По умолчанию, await
Задача захватит этот контекст синхронизации и использует его для возобновления метода. Когда метод возобновляется, он может находиться в другом потоке, но у него будет тот же контекст запроса (включая HttpContext.Current
а также другие вещи, такие как культура и безопасность).
Так, HttpContext.Current
сохраняется, но любой из ваших ThreadStatic
значения нет.
Я опишу как await
работает с SynchronizationContext
в моем async
введение Если вы хотите больше деталей, проверьте мой SynchronizationContext
Статья MSDN (особенно последний раздел по асинхронной CTP).
ThreadStatic вообще не имеет разногласий с асинхронным кодом. В этом контексте он часто используется для повышения производительности, предоставляя каждому потоку собственную копию определенного объекта, устраняя конфликт.
Конечно, это должно быть использовано осторожно. И если у вас есть сценарий, в котором требуется, чтобы один и тот же экземпляр объекта использовался независимо от того, в каком потоке выполняется код, то ThreadStatic либо не будет работать, либо потребует тщательной обработки потоков, чтобы обеспечить выполнение каждого потока выполнения. вернуться к теме, где он принадлежит.
В некоторых сценариях async/await вам гарантировано, что продолжение происходит в том же потоке, что и исходное ожидание. Например, когда вы ожидаете из потока GUI в программе Forms или WPF. Но это не гарантируется функциями async/await в целом.
В конечном итоге, несмотря на то, что вы можете использовать ThreadStatic с async/await, вам все равно нужно убедиться, что вы используете его так, как он предназначен: для привязки определенного значения или объекта к определенному потоку. Это отлично подходит для объектов общего назначения, где любой заданный поток выполнения, который может быть продолжен, не заботится о том, какой объект он фактически использует. Или где вы уверены, что поток выполнения остается в данном потоке.
Но в противном случае нет. Вы не хотите использовать ThreadStatic в сценариях, где в то же время вам необходим поток выполнения, чтобы всегда использовать один и тот же объект, но вы не можете гарантировать, что поток выполнения останется в том же потоке. (Извините, если последнее утверждение кажется очевидным... Я просто хочу убедиться, что оно ясно).
Предполагая, что await
был вызван из дружественного к асинхронному стеку вызовов (асинхронный контроллер / обработчик), тогда да, завершение гарантировано успешно.
ASP.NET SynchronizationContext будет обрабатывать доступность HttpContext в возвращаемых потоках (если вы не используете ConfigureAwait(false)
). Однако это не относится к потокам данных в целом, поэтому предпочитайте HttpContext.Items, если у вас есть такой тип состояния запроса "глобальный".