Как управлять потоковым локальным хранилищем (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 в начале Задачи и сопоставьте его после каждого ожидающего вызова.