Закрытие лямбда-выражения в C # - PullRequest
2 голосов
/ 28 февраля 2012

У меня есть такая функция.

function SomeFunction()
{

    const int NUMBER_OF_CONCURENT_THREADS = 50;

    List<Guid> sessions = new List<Guid>();

    ManualResetEvent[] doneEvents = new ManualResetEvent[NUMBER_OF_CONCURENT_THREADS];

    Action<int> makeServiceCall = (iter) =>
    {
        var proxy = GetProxy();
        sessions.Add(proxy.GetCurrentSessionId());

        doneEvents[iter].Set();
    };

    for (int i = 0; i < NUMBER_OF_CONCURENT_THREADS; ++i)
    {
        doneEvents[i] = new ManualResetEvent(false);                

        ThreadPool.QueueUserWorkItem((o) => 
        {
            int iter = i;
            makeServiceCall(iter);
        });
    }

    WaitHandle.WaitAll(doneEvents);

    Assert.AreEqual(50, sessions.Count);
}

Проблема в том, что я получаю IndexOutOfRangeException при doneEvents[iter].Set(); строке кода. Пожалуйста, есть идеи как это исправить?

1 Ответ

9 голосов
/ 28 февраля 2012

Ах, доброе старое закрытие по переменной итератора; p

Перемещение int iter:

int iter = i;
ThreadPool.QueueUserWorkItem((o) => {
    makeServiceCall(iter);
});

В настоящее время вы захватываете что-то, что меняется с циклом, и обычно после цикла к моменту запуска потоков. В качестве альтернативы используйте arg o:

ThreadPool.QueueUserWorkItem((o) => {
    int iter = (int)o;
    makeServiceCall(iter);
}, i);

Это передает коробочную копию i в момент, когда элемент находится в очереди, поэтому он не изменится.

В случае, если проблема не ясна: переменные, которые записаны в лямбду, сохраняют свою исходную точку объявления - это полное лексическое замыкание. i внутри QueueUserWorkItem - это не «значение i во время создания этой лямбды», а скорее «значение i сейчас ». В большинстве случаев цикл for значительно ускоряет создание / захват потока, поэтому к тому моменту, когда потоки запустились, цикл for завершился, и значение i вышло за пределы для массива.

...