Как управлять локальным хранилищем потоков (TLS) при использовании TPL?

#.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, а также в пул потоков.

http://www.wintellect.com/CS/blogs/jeffreyr/archive/2010/09/27/logical-call-context-flowing-data-across-threads-appdomains-and-processes.aspx

Комментарии:

1. К вашему сведению, этот механизм намного, намного медленнее, чем при использовании TLS, поскольку он сериализует данные при каждом вызове контекста…

Ответ №3:

Конечно, есть еще одна альтернатива: напишите класс TaskLocal (T), как мы это делали, который основывает хранилище на текущей задаче, а не на текущем потоке. Честно говоря, я понятия не имею, почему Microsoft не сделала этого в рамках своей первоначальной реализации задачи.

Важное примечание по реализации: поскольку код задачи, вызывающий await, может быть разделен и возобновлен как другой идентификатор задачи, вам также необходимо сделать то, что мы также сделали, и реализовать метод в TaskLocal(T), который сопоставляет новые идентификаторы задач с предыдущими, затем сохранить исходный идентификатор задачи в начале задачи,и сопоставлять его после каждого вызова await.

Комментарии:

1. Пример кода, демонстрирующий это, получил бы от меня 1 🙂

2. Я посмотрю, что я могу сделать … (:

3. О чем AsyncLocal<T> ?

4. Почему никто из нас не обнаружил это в наших поисках??? Я собираюсь сравнить AsyncLocal<T> поведение с нашим TaskLocal<T> . Я сообщу, выгодно ли это.