大约有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()发布于 2011-06-08 05:46:42
您的(非x64)应用程序的最大内存空间为2 2GB。每个线程至少需要1MB,通常情况下,在达到1000个线程之前,您可以预期OOM。
任务类本身就应该解决这个问题(通过使用ThreadPool)。但是当你的任务耗时太长(> 500ms)时,TP将缓慢地添加线程,几分钟或更长时间后就会失败。
最简单的解决方案可能是查看您的代码中发生这种无界任务创建的地方,看看您是否可以以一种符合您的解决方案的方式进行限制。就像你使用生产者/消费者队列一样,让它成为一个有界队列。
否则,限制MaxThreads,但这是一个迟钝的、应用程序范围的工具。
发布于 2011-11-09 14:56:52
当我尝试测试并行系统的极限时,我自己也遇到了这个问题。oleksii的评论恰到好处(1k线程~= 1 1GB的提交内存)。需要注意的是,这个内存是保留的虚拟地址空间,而不是实际“使用”的内存量。当系统不能提交足够大的连续虚拟地址空间来满足您的请求时,就会发生内存不足异常(在这里插入“内存碎片”修辞)。如果你在windows任务管理器中查看进程的死亡时间,你可能只会看到80-120mb的‘已用’内存。要查看保留了多少虚拟地址空间,请在任务管理器中显示"Memory - Commit Size“列。
简而言之,通过将我的构建配置从x86切换到64位,我能够突破~1k线程的限制。这会将可用的虚拟地址空间量从(大约)2 2GB增加到6TB+ (取决于您的操作系统版本),我的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 );
}
}
}备注:'lWidth‘变量表示当前的并发级别,也就是说,有多少任务同时运行。
总体而言,这是一个有趣的学术实验,但在运行数千个线程提供有用的回报之前,可能还需要‘几年’。将你旋转的线程数量限制为更实际的数量可能是明智的--可能是小于‘数千’的数量级。
发布于 2011-06-08 05:14:32
很可能你同时启动了太多的任务。
每个任务都可能是一个单独的线程。CLR为每个线程分配独立的堆栈内存。我假设一个典型的堆栈需要1024Kb的x64窗口。只需跨越线程,您就可以获得1 1GB的内存,完全用于线程堆栈。这不包括任何堆内存,也不包括大对象堆。另外,您还有其他消耗内存的进程。
https://stackoverflow.com/questions/6271463
复制相似问题