Использование [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 этот словарь хранится где-то в безопасности и устанавливается обратно в поток, когда он выполняет продолжение (или в потоке, я не уверен, что это даже тот же поток, который обрабатывает продолжение), вероятно, с ободряющим похлопыванием по заднице и "иди, возьми их, мальчик!", но эта последняя часть может быть просто моим воображением.

Это несколько точно?

ОБНОВЛЕНИЕ: я попытался собрать небольшой тест, который пытается это. Пока что это работает, и утверждение не провалилось ни для одного из сотен запросов. Кто-нибудь может проверить, имеет ли смысл тест?

https://gist.github.com/anonymous/72d0d6f5ac04babab7b6

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, если у вас есть такой тип состояния запроса "глобальный".

Другие вопросы по тегам