Использование анонимных делегатов с .NET ThreadPool.QueueUserWorkItem - PullRequest
6 голосов
/ 06 марта 2009

Я собирался опубликовать вопрос, но разобрался с ним заранее и решил опубликовать вопрос и ответ - или, по крайней мере, мои наблюдения.

При использовании анонимного делегата в качестве WaitCallback, когда ThreadPool.QueueUserWorkItem вызывается в цикле foreach, создается впечатление, что одно и то же значение foreach передается в каждый поток.

List< Thing > things = MyDb.GetTheThings();
foreach( Thing t in Things)
{
    localLogger.DebugFormat( "About to queue thing [{0}].", t.Id );
    ThreadPool.QueueUserWorkItem(
        delegate()
        {
            try
            {
                WorkWithOneThing( t );
            }
            finally
            {
                Cleanup();
                localLogger.DebugFormat("Thing [{0}] has been queued and run by the delegate.", t.Id ); 
            }
        });
 }

Для коллекции из 16 экземпляров Thing в Things я заметил, что каждое «Thing», переданное WorkWithOneThing, соответствует последнему элементу в списке «вещей».

Я подозреваю, что это потому, что делегат обращается к внешней переменной 't'. Обратите внимание, что я также экспериментировал с передачей Thing в качестве параметра анонимному делегату, но поведение оставалось неправильным.

Когда я повторно проанализировал код для использования именованного метода WaitCallback и передал Thing 't' методу, вуаля ... i-й экземпляр Things был правильно передан в WorkWithOneThing.

Урок параллелизма, наверное. Я также представляю, что семейство Parallel.For решает эту проблему, но эта библиотека не была для нас вариантом в данный момент.

Надеюсь, это спасет кого-то еще.

Говард Хоффман

Ответы [ 3 ]

7 голосов
/ 06 марта 2009

Это правильно и описывает, как C # захватывает внешние переменные внутри замыканий. Это напрямую не проблема параллелизма, а скорее анонимных методов и лямбда-выражений.

В этом вопросе подробно обсуждается эта языковая особенность и ее значение.

1 голос
/ 06 марта 2009

Ниже приведена ссылка, объясняющая, почему это происходит. Он написан для VB, но C # имеет ту же семантику.

http://blogs.msdn.com/jaredpar/archive/2007/07/26/closures-in-vb-part-5-looping.aspx

1 голос
/ 06 марта 2009

Это обычное явление при использовании замыканий и особенно заметно при построении запросов LINQ. Замыкание ссылается на переменную, а не на ее содержимое, поэтому, чтобы ваш пример работал, вы можете просто указать переменную внутри цикла, которая принимает значение t, а затем сослаться на это в замыкании. Это гарантирует, что каждая версия вашего анонимного делегата ссылается на свою переменную.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...