TaskFactory.StartNew -> System.OutOfMemoryException - PullRequest
4 голосов
/ 08 июня 2011

Там работает около 1000 задач, но иногда я получаю следующее исключение из памяти, выбрасываемое планировщиком задач.В чем может быть причина и как ее избежать.

System.Threading.Tasks.TaskSchedulerException: An exception was thrown by a TaskScheduler. ---> System.OutOfMemoryException: Exception of type 'System.OutOfMemoryException' was thrown.
   at System.Threading.Thread.StartInternal(IPrincipal principal, StackCrawlMark& stackMark)
   at System.Threading.Thread.Start(StackCrawlMark& stackMark)
   at System.Threading.Thread.Start(Object parameter)
   at System.Threading.Tasks.ThreadPoolTaskScheduler.QueueTask(Task task)
   at System.Threading.Tasks.Task.ScheduleAndStart(Boolean needsProtection)
   --- End of inner exception stack trace ---
   at System.Threading.Tasks.Task.ScheduleAndStart(Boolean needsProtection)
   at System.Threading.Tasks.Task.InternalStartNew(Task creatingTask, Object action, Object state, CancellationToken cancellationToken, TaskScheduler scheduler, TaskCreationOptions options, InternalTaskOptions internalOptions, StackCrawlMark& stackMark)
   at System.Threading.Tasks.TaskFactory.StartNew(Action action, CancellationToken cancellationToken, TaskCreationOptions creationOptions, TaskScheduler scheduler)
   at App.StartReadSocketTask()

Ответы [ 4 ]

7 голосов
/ 08 июня 2011

Ваше (не x64) приложение имеет максимальный объем памяти 2 ГБ.Каждому потоку требуется минимум 1 МБ, обычно вы можете ожидать OOM, прежде чем достигнете 1000 потоков.

Сам по себе класс Task должен решать эту проблему (используя ThreadPool).Но когда ваши задачи занимают слишком много времени (> 500 мс), TP будет медленно добавлять потоки, и через несколько минут или дольше произойдет сбой.

Самое простое решение - это посмотреть в коде, где происходит это неограниченное создание задач.и посмотреть, можете ли вы ограничить таким образом, чтобы это соответствовало вашему решению.Например, если вы используете Producer / Consumer Que, сделайте его ограниченной очередью.

В противном случае ограничьте MaxThreads, но это тупой инструмент для всего приложения.

5 голосов
/ 08 июня 2011

Я полагаю, что вы столкнулись с забавной частью ThreadPool, где он решил добавить больше рабочих потоков, потому что ваши текущие задачи "истощают" ожидающие задачи.В конечном итоге это приводит к тому, что вашему приложению не хватает памяти.

Я предлагаю добавить флаг TaskCreationOptions.LongRunning при создании.Это позволит ThreadPool знать, что он должен учитывать переподписку задач.

Из книги Параллельное программирование с Microsoft .Net :

В качестве последнего результата вы можете использовать метод SetMaxThreads для настройки класса ThreadPool с верхним пределом числа рабочих потоков, обычно равным количеству ядер.(это свойство Environment.ProcessorCount) ...

Эта же книга также рекомендует следующее Как: создать планировщик задач, ограничивающий степень параллелизма .

4 голосов
/ 09 ноября 2011

Когда я экспериментировал с тестированием границ параллельной системы, я сам столкнулся с этой проблемой. Комментарий oleksii точен (1k потоков ~ = 1 ГБ выделенной памяти). Важно отметить, что эта память является зарезервированным виртуальным адресным пространством, а НЕ объемом фактически используемой памяти. Исключения из нехватки памяти возникают, когда система не может зафиксировать непрерывный фрагмент виртуального адресного пространства, достаточно большого для удовлетворения вашего запроса (вставьте здесь риторику «фрагментация памяти»). Если вы просматриваете процесс в диспетчере задач Windows примерно в то время, когда он умирает, вы можете увидеть всего лишь 80-120 МБ «использованной» памяти. Чтобы узнать, сколько виртуального адресного пространства зарезервировано, откройте столбец «Память - размер фиксации» в диспетчере задач.

Для краткости я смог преодолеть ограничение потока ~ 1k, переключив конфигурацию сборки с x86 на 64 бит. Это увеличивает объем доступного виртуального адресного пространства с (примерно) 2 ГБ до 6 ТБ + (в зависимости от версии вашей ОС), и мое исключение OutOfMemoryException исчезло.

Вот простая программа, которую я создал, которая иллюстрирует этот артефакт, обязательно запустите его как x86 и наблюдайте, как он умирает где-то между потоками 1k и 1.5k - затем переключитесь на 64-битную версию и она должна завершиться без сбоев.

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Threading;

namespace TaskToy
{
    class Program
    {
        static void Main( string[] args )
        {
            List<Task> lTasks = new List<Task>();
            int lWidth = 0;
            for ( int i = 0; i < 5000; i ++ )
            {
                lTasks.Add( new Task( (o) => {

                    Console.WriteLine( "B " + Interlocked.Increment( ref lWidth ) + " tid " + Thread.CurrentThread.ManagedThreadId );
                    Thread.Sleep( 60000 );
                    Console.WriteLine( "E " + Interlocked.Decrement( ref lWidth ) + " tid " + Thread.CurrentThread.ManagedThreadId );
                }, null, TaskCreationOptions.LongRunning ) );
            }

            Parallel.For( 0, lTasks.Count, ( i ) =>
            {
                lTasks[i].Start();
            } );

            Task.WaitAll( lTasks.ToArray() );
            Console.WriteLine( "DONE - press any key..." );
            Console.ReadKey( true );
        }
    }
}

P.S. Переменная 'lWidth' указывает текущий уровень параллелизма, то есть сколько задач фактически выполняется одновременно.

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

2 голосов
/ 08 июня 2011

Вероятно, вы запускаете слишком много задач одновременно.

Каждая задача потенциально является отдельным потоком. CLR назначает независимую стековую память каждому потоку. Я предполагаю, что типичный стек занимает 1024Kb для Windows x64. Просто охватывая потоки, вы получаете 1 ГБ памяти исключительно для стеков потоков. Это не включает в себя кучу памяти или кучу больших объектов. Кроме того, у вас есть другие процессы, которые потребляют память.

...