【问题标题】:Open MediaPlayer - event unhandled in Thread打开 MediaPlayer - 线程中未处理的事件
【发布时间】:2013-08-11 23:40:45
【问题描述】:

我正在尝试创建一个媒体播放器(使用 Media.MediaPlayer() 类),为此我使用线程来处理用户使用 OpenFileDialog 加载的歌曲。我正在使用下一个代码来启动歌曲的处理过程:

    public static List<MediaFile> MediaList = new List<MediaFile>();
    public static Queue<String> MediaFilesQueue = new Queue<String>();



public static void AddMediaFilesToMediaList()
        {

            String pathToFile;


            while (MediaFilesQueue.Count > 0)
          // all the files are loaded into the Queue before processing
            {
            pathToFile = MediaFilesQueue.Dequeue();
            MediaData.MediaList.Add(new MediaFile(pathToFile));

            MediaFileCreator mfCreator = 
                           new MediaFileCreator(MediaData.MediaList.Count - 1);
            mfCreator.CreateNewMediaFile();


            }

}

这是 MediaFileCreator 类:

public class MediaFileCreator
    {

        private int IndexOfMediaFileCurrentlyProcessed;


        public MediaFileCreator(int idx)
        {
            IndexOfMediaFileCurrentlyProcessed = idx;
        }

        public void CreateNewMediaFile()
        {


            var indexOfMediaFileCurrentlyProcessed = IndexOfMediaFileCurrentlyProcessed;

            var tempMediaFile = MediaData.MediaList[indexOfMediaFileCurrentlyProcessed];

            var tempMediaPlayer = new MediaPlayer();
            var waitHandle = new AutoResetEvent(false); 

            //EventHandler eventHandler = delegate(object sender, EventArgs args)
            //{
            //    waitHandle.Set();
            //};


            tempMediaPlayer.MediaOpened += (sender, args) => waitHandle.Set();

            tempMediaPlayer.Open(new Uri(tempMediaFile.PathToFile));

            waitHandle.WaitOne();


            //while (!tempMediaPlayer.NaturalDuration.HasTimeSpan)
            //{
            //    Thread.Sleep(100);
            //}


            var tempTimeSpan = tempMediaPlayer.NaturalDuration.TimeSpan;
            var hasVideo = tempMediaPlayer.HasVideo;
            var hasAudio = tempMediaPlayer.HasAudio;

            MediaData.MediaList[indexOfMediaFileCurrentlyProcessed].HasVideo = hasVideo;
            MediaData.MediaList[indexOfMediaFileCurrentlyProcessed].HasAudio = hasAudio;
            MediaData.MediaList[indexOfMediaFileCurrentlyProcessed].TimeSpanOfMediaFile
                                                                        = tempTimeSpan;


        } 

这是 MediaFile 类:

 public class MediaFile
    {
        public bool HasAudio = false;
        public bool HasVideo = false;

        public TimeSpan TimeSpanOfMediaFile;

        public String PathToFile = null;

        public MediaFile(String pathToFile)
        {
            PathToFile = pathToFile;
        }
    }

我的问题是程序停在waitHandle.WaitOne(); 并尝试一次又一次地运行该行。我通过将该事件处理程序附加到 Open 事件来尝试其他变体,例如第一个注释部分中的变体,但结果是相同的:waitHandle.Set(); 从不执行,并且 waitHandle 的值始终为 false。我设法开始工作的唯一选择是第二个评论部分的解决方案:阻塞线程(使用 Thread.Sleep)直到文件完全加载(例如在初始化 TimeSpan 时加载文件)......这是我的应用程序的时间损失和性能下降。问题显然不在于事件本身,因为如果它在主线程上运行,事件就会触发(从 BackgroundWorker 线程调用 AddMediaFilesToMediaList() 方法,当它检测到有元素时在新线程中启动该方法队列; AddMediaFilesToMediaList() 线程是用 new Thread() 创建的,显然文件正在加载,因为 TimeSpan 已初始化。我真的很想让应用程序使用 waitHandle 或类似的东西工作。我不想使用 Thread.Sleep() 因为它很丑,而且当我尝试加载大量文件时我的应用程序也存在内存泄漏(我占用了 1.2 GB 的内存并且它因错误而停止(OutOfMemory) - 我尝试在其中加载 2048 首歌曲),我相信这可能是因为 Thread.Sleep()。即使不是,调试起来也会容易得多,而不会出现 Thread.Sleep() 的问题。

那么我怎样才能让 waitHandle 工作呢?我怎样才能让waitHandle.Set(); 运行?如果有人知道过多的内存使用可能来自哪里,那就太好了! (我个人认为这是 Thread.Sleep() 的错,但我不知道如何摆脱它)。

编辑:我使用 MediaFileCreator 对象的原因是最初我想使用 2 到 4 个线程的线程池来处理媒体文件,但我遇到了同样的问题,所以我删除了 ThreadPool 并尝试使用代码上面贴了,但同样的问题发生了。

编辑:我设法让它使用第二个线程来等待事件(现在不是最干净的代码,但我会做对的)。

public class MediaFileCreator
{

    private AutoResetEvent openedEvent = new AutoResetEvent(false);


    public MediaFile CreateNewMediaFile(string filename)
    {

        var mFile = new MediaFile(filename);
        var thread = new Thread(WaitForEvent);
        const int maxTimeToWait = 2000;
        openedEvent.Reset();
        thread.Start(mFile);

        var mediaPlayer = new MediaPlayer();

        mediaPlayer.Open(new Uri(mFile.PathToFile));

        openedEvent.WaitOne(maxTimeToWait);

        var fromThread = Dispatcher.FromThread(Thread.CurrentThread);
        if (fromThread != null) fromThread.InvokeShutdown();

        return mFile;

    }

    private void WaitForEvent(object context)
    {

        var mFile = (MediaFile)context;
        var mediaPlayer = new MediaPlayer();

        mediaPlayer.MediaOpened +=
            delegate
            {
                if (mediaPlayer.NaturalDuration.HasTimeSpan)
                    mFile.TimeSpanOfMediaFile = mediaPlayer.NaturalDuration.TimeSpan;
                mFile.HasAudio = mediaPlayer.HasAudio;
                mFile.HasVideo = mediaPlayer.HasVideo;
                mFile.Success = true;
                mediaPlayer.Close();
                openedEvent.Set();
            };

        mediaPlayer.MediaFailed +=
            delegate
            {
                mFile.Failure = true;
                mediaPlayer.Close();
                openedEvent.Set();
            };

        mediaPlayer.Open(new Uri(mFile.PathToFile));

        Dispatcher.Run();
    }
}

【问题讨论】:

  • 查看我完成的答案。

标签: c# multithreading memory-leaks event-handling media-player


【解决方案1】:

有一点是肯定的:Thread.Sleep 调用与您的内存问题无关。

我建议您多清理一下代码。您无需为每个加载的文件创建一个新的MediaCreator 和一个新的MediaPlayer。这可能是内存使用的很大一部分,因为您正在创建所有这些 MediaPlayer 对象而不是关闭它们。垃圾收集器最终会清理它们,但同时它会使您的内存使用量看起来很大。

考虑一下:

public static void AddMediaFilesToMediaList()
{
    MediaFileCreator mfCreator = new MediaFileCreator();

    while (MediaFilesQueue.Count > 0)
    {
        // all the files are loaded into the Queue before processing
        string pathToFile = MediaFilesQueue.Dequeue();

        MediaFile mf = mfCreator.CreateNewMediaFile(pathToFile);

        MediaData.MediaList.Add(mf);
    }
}

public class MediaFileCreator
{
    private MediaPlayer player = new MediaPlayer();
    private ManualResetEvent openedEvent = new ManualResetEvent(false);

    public MediaFileCreator()
    {
        player.MediaOpened = MediaOpened;
    }

    private void MediaOpened(object sender, EventArgs args)
    {
        openedEvent.Set();
    }

    public MediaFile CreateNewMediaFile(string filename)
    {
        openedEvent.Reset();

        player.Open(new Uri(tempMediaFile.PathToFile));

        // wait for it to load
        openedEvent.WaitOne();

        MediaFile mf = new MediaFile(filename);
        mf.HasVideo = player.HasVideo;
        mf.HasAudio = player.HasAudio;
        mf.TimeSpanOfMediaFile = player.NaturalDuration.TimeSpan;

        player.Close();

        return mf;
    }
}

这大大简化了事情,并且只使用了一个 MediaPlayer 对象,这应该会减少相当多的内存使用量。

我真的不知道您的AutoResetEvent 不起作用是什么问题。它看起来正确。

在这个新代码中,您可以在waitHandle.Set 调用上放置一个断点,以查看它是否真的被命中。我不知道MediaOpened 事件究竟是什么时候触发的,也不知道当事件引发时玩家的状态应该是什么。文档对此保持沉默。

问题可能是MediaPlayer 希望其代码在 UI 线程上执行。我对 WPF 控件还不够熟悉。您可以调用VerifyAccess 来确定您的线程是否可以访问该对象。

【讨论】:

  • 是的,就是这么说的 (msdn.microsoft.com/en-us/library/…)。据我所知,该事件发生在 MediaPlayer 对象选择所需信息之后:Timespan、HasAudio、HasVideo 和其他类似的东西。好的,所以我会在早上首先测试您的代码,但是如果我遇到同样的问题该怎么办?我无法在 UI 线程上加载所有数千首歌曲。
  • @TheQuestioner,如果ManualResetEventAutoResetEvent 不起作用,您可以尝试使用Thread.Sleep(10) 循环(而不是100)。这很难看,但从网上环顾四周,这是我能看到的唯一解决方案。
  • 它不起作用,但我确实设法找到了解决方案(通过使用第二个线程等待事件和调度程序)。我在初始文本中添加了解决方案。顺便谢谢你,你的解决方案帮了大忙!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-11
  • 2020-01-27
相关资源
最近更新 更多