Как управлять потоковым локальным хранилищем (TLS) при использовании TPL?
Я хочу сохранить информацию контекста ведения журнала в TLS, чтобы я мог установить значение в точке входа и иметь это значение во всех полученных пакетах. Это хорошо работает, но я также использую TPL и ThreadPool. Затем возникает проблема переноса данных TLS в другие потоки. Я могу сделать все это сам, но потом я теряю хорошие методы, такие как Parallel.For.
Есть ли способ скопировать TLS при использовании TPL? Это также относится к С#, когда он получает функцию ожидания.
Спасибо,
Erick
Ответы
Ответ 1
Обычно это обрабатывается с помощью перегрузки Parallel.For, которая уже предоставляет локальные данные потока.
Эта перегрузка позволяет вам предоставить инициализацию и делегат финализации, который фактически становится инициализацией для каждого потока для ваших локальных данных потока, а функция сокращения в конце объединяет результаты вместе (которая запускается один раз на поток). Я писал об этом подробнее здесь.
Основная форма - сделать что-то вроде:
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;
});
Ответ 2
Я нашел другое решение проблемы, которое не требует кода. Я смог использовать CallContext для привязки данных к "логическому потоку". Эти данные передаются из начального потока в потоки, сгенерированные TPL, а также ThreadPool.
http://www.wintellect.com/CS/blogs/jeffreyr/archive/2010/09/27/logical-call-context-flowing-data-across-threads-appdomains-and-processes.aspx
Ответ 3
Существует, конечно, еще одна альтернатива: напишите класс TaskLocal (T), как и мы, который основывает память на текущей Задаче, а не на текущем потоке. Честно говоря, я понятия не имею, почему Microsoft не делала этого как часть своей первоначальной реализации Task.
Важное примечание к реализации. Поскольку код задачи, который вызывает ожидание, может быть разделен и возобновлен как другой TaskId, вам также необходимо выполнить то, что мы также сделали, и реализовать метод в TaskLocal (T), который сопоставляет новые TaskIds с предыдущими, затем сохраните исходный TaskId в начале Задачи и сопоставьте его после каждого ожидающего вызова.