Open MediaPlayer - event unhandled in Thread

2019-02-27 19:48发布

问题:

I'm trying to create a media player (with Media.MediaPlayer() class) and for this I am using a thread to process the songs that are loaded by the user using the OpenFileDialog. I am using the next code to start the process the songs:

    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();


            }

}

And this is the MediaFileCreator class:

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;


        } 

And this is the MediaFile class:

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

        public TimeSpan TimeSpanOfMediaFile;

        public String PathToFile = null;

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

My problem is that the program stops at waitHandle.WaitOne(); and tries to run that line again and again. I tried other variants like the one in the first commented section by attaching that event handler to the Open event, but the result was the same: waitHandle.Set(); never executes and the value of the waitHandle is always false. The only option that I managed to get working is the the solution from the second commented section: blocking the thread (with Thread.Sleep) until the file is completely loaded (the file is loaded for example when the TimeSpan is initialised) ... which is time lost and performance decrease for my application. The problem is obviously not with the event itself, because the event triggers if it's run on the main thread (the method AddMediaFilesToMediaList() is called from a BackgroundWorker Thread that launches the method inside a new thread when it detects that there are elements in the Queue; the AddMediaFilesToMediaList() thread is created with new Thread() ) and obviously the file is loading because the TimeSpan is initialized. I really want to make the application work using the waitHandle or something like that. I don't want to use the Thread.Sleep() because it's ugly and my application also has a memory leak when I try to load a lot of files (i takes over 1.2 GB of memory and the it stops with errors (OutOfMemory) - I tried to load 2048 songs in it) and I believe that it could be because of the Thread.Sleep(). Even if it isn't, it will be much more easy to debug without the problem of the Thread.Sleep().

So how can I make the waitHandle to work ? How can I make the waitHandle.Set(); to run ? And if anyone has any idea about where that that excessive memory usage could come from, it would be great ! (I personally believe that it's the fault of Thread.Sleep() but I don't know how to get rid of it).

Edit : The reason I use an MediaFileCreator object is that initially I wanted to use a thread pool of 2 to 4 threads to process the media files, but I had the same problem so I removed the ThreadPool and I tried with the code posted above, but the same problem occurred.

Edit : I managed to get it working using a second thread to wait for the event (not the cleanest code right now, but I'll make it right).

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();
    }
}

回答1:

One thing is for certain: the Thread.Sleep call has nothing to do with your memory problem.

I would suggest that you clean up your code a little bit more. There's no need for you to be creating a new MediaCreator and a new MediaPlayer for every file you load. That could be a large part of the memory use, since you're creating all those MediaPlayer objects and not closing them. The garbage collector will eventually clean them up, but it can make your memory use look huge in the meantime.

Consider this:

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;
    }
}

That simplifies things quite a bit and uses only one MediaPlayer object, which should cut down quite a bit on the memory usage.

I really don't know what the problem is with your AutoResetEvent not working. It looks right.

In this new code, you can put a breakpoint on the waitHandle.Set call to see if it's actually hit. I don't know when exactly the MediaOpened event is triggered, or what the state of the player is supposed to be when the event is raised. The documentation is suspiciously silent about that.

It's possible the problem is that MediaPlayer wants its code to be executing on the UI thread. I'm not familiar enough with the WPF controls to say. You might call VerifyAccess to determine if your thread has access to the object.