我正在尝试创建一个具有播放列表选项的媒体播放器。加载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'的未处理的异常”。

现在解释一下“加载歌曲”在我的应用程序中的含义:
  • 一个线程,该线程遍历从OpenFileDialog加载的每首歌曲,并检查文件的扩展名是否已知以及扩展名(在本例中为mp3),并在队列末尾合并文件的路径。
  • 另一个线程验证队列中是否有任何元素。
  • 如果存在,它将提取第一个元素,并且如果CpuLoad和MemoryLoad(由另一个线程计算)不​​是太高,它将启动一个新线程,该线程进行一些操作(表示为4)
  • 进行操作的线程将歌曲加载到System.Windows.Media.MediaPlayer类中,并验证以下内容:文件的TimeSpan(如果文件具有音频,如果文件具有视频,并记住这3个变量以及列表中文件的路径。
  • 还有另一个线程,用于验证是否有一些线程完成了他们的工作并将媒体文件添加到列表中,如果有的话,它将删除对它们的引用,以便垃圾收集器将其处理。

  • 在下一行中显示“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显示的一些诊断信息:
  • http://s24.postimg.org/e3e8cfcit/image1.png
  • http://s24.postimg.org/71gaq88x1/image2.png
  • 最佳答案

    您没有说如何启动线程,但是听起来就像您正在创建一个线程(即new Thread(...)并启动它。如果是这种情况,那么您正在创建数百个或数千个线程,每个线程都是尝试加载并验证歌曲,这会导致一些严重的问题:

  • 将所有这些歌曲一次存储在内存中很可能会导致您内存不足。
  • 拥有数百个线程,计算机花费大量时间进行线程上下文切换,让线程1运行一段时间,然后运行线程2,然后运行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
    

    您线程的代码非常简单:
    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内核是没有道理的,而且正如我之前说的那样,限制因素很可能是您的磁盘驱动器。

    09-28 01:53