多线程的巨大内存和CPU使用率

The*_*ner 1 c# multithreading memory-management media-player threadpool

我正在尝试创建一个具有播放列表选项的媒体播放器.当一个负载10-20首歌没有问题.所以我尝试了更苛刻的东西:我试着加载2048首歌曲(我拍了好几首歌并复制了很多次).试图将它们加载到我的媒体播放器中,我的CPU和Ram内存增长了95%以上(只加载了前250首歌曲),有一次我的电脑重启了.所以我试图通过使用不让应用程序接管计算机的东西来减慢操作:如果CPU负载超过85%并且内存负载超过90%(我使用64位),我会停止加载新歌曲操作系统与Windows 8,如果这很重要).它在某种程度上起作用,允许我加载近600首歌曲然后:

A first chance exception of type 'System.InvalidOperationException' occurred in mscorlib.dll
A first chance exception of type 'System.InvalidOperationException' occurred in mscorlib.dll
A first chance exception of type 'System.InvalidOperationException' occurred in mscorlib.dll
A first chance exception of type 'System.InvalidOperationException' occurred in mscorlib.dll
A first chance exception of type 'System.InvalidOperationException' occurred in mscorlib.dll
The thread 'vshost.NotifyLoad' (0x1d0c) has exited with code 0 (0x0).
The thread 'vshost.LoadReference' (0x1e48) has exited with code 0 (0x0).
A first chance exception of type 'System.OutOfMemoryException' occurred in mscorlib.dll
A first chance exception of type 'Microsoft.VisualStudio.Debugger.Runtime.CrossThreadMessagingException' occurred in Microsoft.VisualStudio.Debugger.Runtime.dll
Run Code Online (Sandbox Code Playgroud)

最后,应用程序停止在"mscorlib.dll中发生了'System.OutOfMemoryException'类型的未处理异常".

现在解释一下"加载歌曲"在我的应用程序中意味着什么:

  1. 一个线程迭代从OpenFileDialog加载的每首歌曲,并检查文件的扩展名是否已知,如果是(并且在这种情况下它是:mp3),它将合并队列末尾的文件路径.
  2. 另一个线程验证队列中是否有任何元素.
  3. 如果有的话,它会提取第一个元素,如果CpuLoad和MemoryLoad(由另一个线程计算)不​​是太高,它会启动一个新线程进行一些操作(在4处显示)
  4. 使操作加载歌曲的线程在System.Windows.Media.MediaPlayer类中并验证接下来的事情:文件的TimeSpan,文件是否有音频以及文件是否有视频并记住这3个变量以及路径列表中的文件.
  5. 还有另一个线程可以验证是否有线程已完成其工作并已将媒体文件添加到List中,如果有,则删除对它们的引用,以便垃圾收集器处理它们.

"mscorlib.dll中出现类型'System.OutOfMemoryException'的未处理异常"出现在下一行:

MediaCreator[idx].CreatorThread.Start();
Run Code Online (Sandbox Code Playgroud)

这将是启动处理歌曲的线程的线.所以我做了下一件事:在上面发布的行之前,我补充道Thread.Sleep(100);.它工作(这实际上导致加载所有2048个文件),除了(根据我添加的秒表)加载所有歌曲需要3分28秒的事实.此外,我知道Thread.Sleep通常不是推荐的方法,我知道同样的人甚至认为是弱编程技能的证明(我不知何故同意他们).我不想使用这种方法,因为它显然需要很长时间,并且在每台计算机/ cpu/hdd/ram上工作都是不值得信任的.为了证明这种不可信度,我用Sleep(10)测试了它很快就失败了,而睡眠(20)用它加载了近1000首歌曲再次失败.我还尝试将CPU负载降低到15%,将内存负载降低到80%,但加载的歌数不超过1300(这也证明效率不高,因为CPU负载的短峰值为60%).

我还想提一下,Winamp使用大约11%的CPU(从5%到16%)和不到40 MB的内存,在30秒内加载了所有文件.

所以我的问题是:我该怎么办?我可以限制线程数,以便不超过X个线程以相同的类型运行,但这似乎也证明了编程技巧不强,因为不是每个CPU都可以保持相同数量的运行线程.所以我该怎么做 ?我真的需要从歌曲中获取这些细节 - 尽可能用尽可能少的资源(知道它们有多长,如果它们是音频或视频文件:在这里我应该提到我的应用程序也播放电影,它是只是因为我不认为任何人需要在应用程序中同时加载数千部电影,如果我解决了音频问题,它也将解决视频,因为歌曲只有在电影存储到列表之前才会被电影区分开来 - 所以没有什么可以解决我的问题的解决方案).我真的需要帮助才能解决这个问题.

编辑:我还附上ANTS Performance Profiles显示的一些诊断信息:

  1. http://s24.postimg.org/e3e8cfcit/image1.png
  2. http://s24.postimg.org/71gaq88x1/image2.png

Jim*_*hel 5

你没有说你是如何开始线程的,但听起来你正在创建一个线程(即new Thread(...)启动它.如果是这样的话,那你就创建了数百个甚至数千个线程,每个线程都在尝试加载歌曲并验证它.这会导致一些严重的问题:

  1. 将所有这些歌曲同时存储在内存中很可能会导致内存不足.
  2. 有数百个线程,计算机花费大量时间进行线程上下文切换,让线程1运行一点,然后线程2,然后3,等等.很可能你的计算机正在颠簸 - 花更多时间做线程上下文开关比做实际工作.
  3. 您从中加载文件的磁盘驱动器一次只能执行一项操作.如果两个线程要求加载文件,其中一个将不得不等待.因为读取文件可能比任何处理花费的时间更长,所以让多个线程执行此工作的可能性不大.

你的设计严重过于复杂.您可以通过使用单个线程来简化它,减少内存需求,并可能提高处理速度.但是如果一个线程慢两个,你可能想要的不超过三个:

一个线程(主线程)获取文件名,检查它们,并将它们放在队列中.这是您列表中的第1步.

两个消费者线程读取队列并完成剩余的处理.这些消费者线程中的每一个都在队列上等待并执行步骤4(加载文件,进行处理,并将结果添加到列表中).

对于BlockingCollection来说,这种事情非常容易,这是一个并发队列.基本思路是:

// this is the output list
List<MusicRecord> ProcessedRecords = new List<MusicRecord>();
object listLock = new object();  // object for locking the list when adding

// queue of file names to process
BlockingCollection<string> FilesToProcess = new BlockingCollection<string>();

// code for main thread

// Start your consumer threads here.

List<string> filesList = GetFilesListFromOpenDialog(); // however you do this
foreach (string fname in filesList)
{
    if (IsGoodFilename(fname))
    {
        string fullPath = CreateFullPath(fname);
        FilesToProcess.Add(fullPath); // add it to the files to be processed
    }
}
// no more files, mark the queue as complete for adding
// This marks the "end of the queue" so that clients reading the queue
// know when to stop.
FilesToProcess.CompleteAdding();

// here, wait for threads to complete
Run Code Online (Sandbox Code Playgroud)

线程的代码非常简单:

foreach (var fname in FilesToProcess.GetConsumingEnumerable())
{
    // Load file and process it, creating a MusicRecord
    // Then add to output
    lock (listLock)
    {
        ProcessedRecord.Add(newRecord);
    }
}
Run Code Online (Sandbox Code Playgroud)

这就是所有线程需要做的事情.GetConsumingEnumerable队列中的句柄等待(非忙),对项目进行排队,以及当队列已知为空时退出.

使用此设计,您可以从单个使用者线程开始,并根据需要扩展到任意数量.但是,拥有比CPU内核更多的线程是没有意义的,正如我之前所说的,限制因素很可能是你的磁盘驱动器.