Пока я использовал Parallel.ForEach в своей программе, я обнаружил, что некоторые потоки никогда не заканчивались. Фактически, он продолжал размножать новые темы снова и снова, поведение, которого я не ожидал и определенно не хотел.
Я смог воспроизвести это поведение со следующим кодом, который, как и моя "настоящая" программа, часто использует процессор и память (код .NET 4.0):
public class Node
{
public Node Previous { get; private set; }
public Node(Node previous)
{
Previous = previous;
}
}
public class Program
{
public static void Main(string[] args)
{
DateTime startMoment = DateTime.Now;
int concurrentThreads = 0;
var jobs = Enumerable.Range(0, 2000);
Parallel.ForEach(jobs, delegate(int jobNr)
{
Interlocked.Increment(ref concurrentThreads);
int heavyness = jobNr % 9;
//Give the processor and the garbage collector something to do...
List<Node> nodes = new List<Node>();
Node current = null;
for (int y = 0; y < 1024 * 1024 * heavyness; y++)
{
current = new Node(current);
nodes.Add(current);
}
TimeSpan elapsed = DateTime.Now - startMoment;
int threadsRemaining = Interlocked.Decrement(ref concurrentThreads);
Console.WriteLine("[{0:mm\\:ss}] Job {1,4} complete. {2} threads remaining.", elapsed, jobNr, threadsRemaining);
});
}
}
При запуске на моем четырехъядерном процессоре сначала начинается с 4 одновременных потоков, как и следовало ожидать. Однако со временем создается больше потоков. В конце концов, эта программа затем выдает исключение OutOfMemoryException:
[00:00] Job 0 complete. 3 threads remaining.
[00:01] Job 1 complete. 4 threads remaining.
[00:01] Job 2 complete. 4 threads remaining.
[00:02] Job 3 complete. 4 threads remaining.
[00:05] Job 9 complete. 5 threads remaining.
[00:05] Job 4 complete. 5 threads remaining.
[00:05] Job 5 complete. 5 threads remaining.
[00:05] Job 10 complete. 5 threads remaining.
[00:08] Job 11 complete. 5 threads remaining.
[00:08] Job 6 complete. 5 threads remaining.
...
[00:55] Job 67 complete. 7 threads remaining.
[00:56] Job 81 complete. 8 threads remaining.
...
[01:54] Job 107 complete. 11 threads remaining.
[02:00] Job 121 complete. 12 threads remaining.
..
[02:55] Job 115 complete. 19 threads remaining.
[03:02] Job 166 complete. 21 threads remaining.
...
[03:41] Job 113 complete. 28 threads remaining.
<OutOfMemoryException>
График использования памяти для эксперимента выше выглядит следующим образом:
(Снимок экрана на голландском языке, верхняя часть - использование процессора, использование памяти нижней части). Как вы можете видеть, похоже, что новый поток генерируется почти каждый раз, когда сборщик мусора мешает (как можно увидеть в результате падения памяти).
Может кто-нибудь объяснить, почему это происходит, и что я могу с этим поделать? Я просто хочу, чтобы .NET прекратила создавать новые потоки и сначала закончила существующие потоки...