Это старая модифицированная проблема закрытия.Возможно, вы захотите посмотреть: Threadpools - возможная проблема порядка выполнения потоков для аналогичного вопроса и сообщение в блоге Эрика Липперта Закрытие переменной цикла, которая считается вредной , для понимания проблемы.
По сути, лямбда-выражение, которое вы там получаете, захватывает переменную s
, а не значение переменной в точке, где объявлена лямбда.Следовательно, последующие изменения, внесенные в значение переменной , являются видимыми для делегата.Экземпляр Sample
, в котором будет выполняться метод RunCount
, будет зависеть от экземпляра , на который ссылается переменная s
(ее значение) в точке, в которой делегат фактически выполняет .
Кроме того, поскольку делегаты (компилятор фактически использует один и тот же экземпляр делегата) выполняются асинхронно, не гарантируется, что эти значения будут находиться в точке каждого выполнения.В настоящее время вы видите, что цикл foreach
завершается в главном потоке до любого вызова делегата (как и следовало ожидать - требуется время для планирования задач в пуле потоков).Таким образом, все рабочие элементы в конечном итоге увидят «окончательное» значение переменной цикла.Но это не гарантируется никакими средствами;попробуйте вставить разумную длительность Thread.Sleep
внутри цикла, и вы увидите другой вывод.
Обычное исправление:
- Введите другую переменную внутри тело цикла.
- Назначьте эту переменную текущему значению переменной цикла.
Захват переменной «copy» вместо переменной цикла внутри лямбды.
foreach (Sample s in arrSample)
{
Sample sCopy = s;
ThreadPool.QueueUserWorkItem(callback => sCopy.RunCount());
}
Теперь каждый рабочий элемент «владеет» определеннымзначение переменной цикла.
Другой вариант в этом случае - полностью избежать проблемы, не захватывая ничего:
ThreadPool.QueueUserWorkItem(obj => ((Sample)obj).RunCount(), s);