#.net #asynchronous #threadpool #task-parallel-library #thread-local-storage
#.net #асинхронный #threadpool #задача-параллельная-библиотека #thread-local-storage
Вопрос:
Я хочу сохранить информацию о контексте ведения журнала в TLS, чтобы я мог установить значение в точке входа и иметь это значение доступным во всех результирующих стеках. Это работает хорошо, но я также использую TPL и ThreadPool. Затем возникает проблема переноса данных TLS в другие потоки. Я могу сделать все это сам, но тогда я теряю хорошие методы, такие как Parallel.For.
Есть ли какой-нибудь способ скопировать TLS при использовании TPL? Это также будет применяться к C #, когда он получит функцию ожидания.
Спасибо, Эрик
Ответ №1:
Обычно это обрабатывается с помощью перегрузки Parallel.Для этого уже предусмотрены локальные данные потока.
Эта перегрузка позволяет вам предоставить делегат инициализации и завершения, который фактически становится инициализацией для каждого потока для локальных данных вашего потока, и функцию сокращения в конце для «объединения» результатов вместе (которая выполняется один раз для каждого потока). Я подробно писал об этом здесь .
Основная форма — сделать что-то вроде:
object sync = new object();
double result = 0;
Parallel.For(0, collection.Count,
// Initialize thread local data:
() => new MyThreadSpecificData(),
// Process each item
(i, pls, currentThreadLocalData) =>
{
// Generate a NEW version of your local state data
MyThreadSpecificData newResults = ProcessItem(collection, i, currentThreadLocalData);
return newResults;
},
// Aggregate results
threadLocalData =>
{
// This requires synchronization, as it happens once per thread,
// but potentially simultaneously
lock(sync)
result = threadLocalData.Results;
});
Комментарии:
1. Спасибо Reed — это делает то, что я хотел, однако я нашел другой способ решения проблемы. Тем не менее, это отличный материал, который я скоро буду использовать.
2. Интересно, почему они включили такую уродливую перегрузку? С такой скоростью было бы проще просто инициализировать локальные данные потока и деинициализировать их в вашем главном делегате. Если они не оптимизировали его ..?
3. @TimLovell-Smith Локальные данные потока повторно используются при вызовах нескольких делегатов, поэтому их нельзя инициализировать / доработать внутри одного делегата. (В этом и суть ;))
Ответ №2:
Я нашел другое решение проблемы, которое не требует кода. Я смог использовать CallContext для прикрепления данных к «логическому потоку». Эти данные передаются из начального потока в потоки, созданные TPL, а также в пул потоков.
Комментарии:
1. К вашему сведению, этот механизм намного, намного медленнее, чем при использовании TLS, поскольку он сериализует данные при каждом вызове контекста…
Ответ №3:
Конечно, есть еще одна альтернатива: напишите класс TaskLocal (T), как мы это делали, который основывает хранилище на текущей задаче, а не на текущем потоке. Честно говоря, я понятия не имею, почему Microsoft не сделала этого в рамках своей первоначальной реализации задачи.
Важное примечание по реализации: поскольку код задачи, вызывающий await, может быть разделен и возобновлен как другой идентификатор задачи, вам также необходимо сделать то, что мы также сделали, и реализовать метод в TaskLocal(T), который сопоставляет новые идентификаторы задач с предыдущими, затем сохранить исходный идентификатор задачи в начале задачи,и сопоставлять его после каждого вызова await.
Комментарии:
1. Пример кода, демонстрирующий это, получил бы от меня 1 🙂
2. Я посмотрю, что я могу сделать … (:
3. О чем
AsyncLocal<T>
?4. Почему никто из нас не обнаружил это в наших поисках??? Я собираюсь сравнить
AsyncLocal<T>
поведение с нашимTaskLocal<T>
. Я сообщу, выгодно ли это.