我正在尝试创建一个具有播放列表选项的媒体播放器。加载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
最后,应用程序停止在“mscorlib.dll中发生类型'System.OutOfMemoryException'的未处理的异常”。
现在解释一下“加载歌曲”在我的应用程序中的含义:
在下一行中显示“mscorlib.dll中发生了'System.OutOfMemoryException类型的未处理的异常”:
MediaCreator[idx].CreatorThread.Start();
那将是启动处理歌曲的线程的行。因此,我做了下一件事:在上面的行之前,我添加了
Thread.Sleep(100);
。它的工作原理(这样做实际上导致加载了所有2048个文件),但事实是(根据我添加的秒表),加载所有歌曲花费了3分28秒,这一事实除外。另外,我知道Thread.Sleep通常不是推荐的方法,并且我知道同一个人甚至认为这是编程能力较弱的证明(我以某种方式同意他们的观点)。我也不想使用此方法,因为它显然要花费很长时间,并且在每台计算机/cpu/hdd/ram上运行都是不可靠的。为了证明这一点的不可信性,我测试了Sleep(10)使其快速失败,并测试了Sleep(20)使其在再次失败之前加载了近1000首歌曲。我还尝试将CPU负载降低到15%,将内存负载降低到80%,但是加载的歌曲不超过1300首(由于CPU负载的峰值只有60%,因此这也被证明是无效的)。我还想提到Winamp在30秒内用了大约11%的CPU(从5%到16%)和不到40 MB的内存来加载了所有文件。
所以我的问题是:我应该如何进行?我可以限制线程的数量,以使在同一类型下运行的线程数不超过X个,但是在我看来,这也证明了编程能力很弱,因为并非每个CPU都可以容纳相同数量的正在运行的线程。所以我该怎么做 ?我真的需要从歌曲中获取这些细节-尽可能长地使用尽可能少的资源(要知道它们有多长,以及它们是否是音频或视频文件:在这里我应该提一下,我的应用程序还可以播放电影,只是我不认为任何人都需要一次在应用程序中加载数千个电影,如果我解决了音频问题,那么视频也将得到解决,这是因为歌曲只有在存储在列表中之前才被电影区分-因此,不会与我的问题的解决方案发生冲突)。我真的需要帮助来解决这个问题。
编辑:我还附上了ANTS Performance Profiles显示的一些诊断信息:
最佳答案
您没有说如何启动线程,但是听起来就像您正在创建一个线程(即new Thread(...)
并启动它。如果是这种情况,那么您正在创建数百个或数千个线程,每个线程都是尝试加载并验证歌曲,这会导致一些严重的问题:
您的设计过于复杂。您可以使用一个线程来简化它,减少内存需求,并可能提高处理速度。但是,如果一个线程慢两个,那么您可能希望不超过三个:
一个线程(主线程)获取文件名,对其进行检查,然后将其放置在队列中。这是您列表中的第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
您线程的代码非常简单:
foreach (var fname in FilesToProcess.GetConsumingEnumerable())
{
// Load file and process it, creating a MusicRecord
// Then add to output
lock (listLock)
{
ProcessedRecord.Add(newRecord);
}
}
这就是线程需要做的所有事情。
GetConsumingEnumerable
处理队列中的等待(不忙),使项目出队以及在已知队列为空时退出。通过这种设计,您可以从单个使用者线程开始,然后扩展到所需的任意数量。但是,拥有更多线程而不是拥有CPU内核是没有道理的,而且正如我之前说的那样,限制因素很可能是您的磁盘驱动器。