使用WMPLib C#.NET

时间:2017-09-20 09:37:59

标签: c# .net multithreading iis wmplib

我遇到的问题是我的网络应用程序经常崩溃并重置IIS中的应用程序池,导致严重的性能问题,以及擦除我的应用程序中运行的任何计时线程。

该站点是在AWS的2012 Windows Server EC2实例上运行的.NET 4.5.2 C#MVC5站点。

当我开始看到网站在运行这么多分钟后加载时,首先注意到这个问题。我认为它可能是ApplicationPool回收并确保在IIS中正确设置IdleTime和Application Preload。这个问题仍然存在。

接下来,我前往服务器管理器检查事件日志,发现这些条目大约每15分钟左右发生一次:

  

错误应用程序名称:w3wp.exe,版本:8.5.9600.16384,时间   标记:0x5215df96错误模块名称:WMNetMgr.dll_unloaded,   版本:12.0.9600.17415,时间戳:0x545047db异常代码:   0xc0000005故障偏移:0x00000000000cf5cf故障进程ID:   0x17d0错误应用程序启动时间:0x01d331dc20f096d0错误   应用程序路径:c:\ windows \ system32 \ inetsrv \ w3wp.exe故障模块   路径:WMNetMgr.dll报告ID:777a35de-9dd1-11e7-81d7-025ff0be916d   错误包全名:错误包相关应用程序ID:

  

WIN-7PCRJOFR05F 5011警告Microsoft-Windows-WAS系统9/20/2017   7:01:04 AM - 为应用程序池提供服务的流程' SiteName'遭遇了   Windows进程激活服务导致致命的通信错误。   流程ID为' 6096'数据字段包含错误编号。

接下来我运行了DebugDiag2 Collection and Analysis:

  

警告 - DebugDiag无法找到调试符号   WMNetMgr.dll>,因此以下信息可能不完整。   在   w3wp__SiteName__PID__5088__Date__09_20_2017__Time_06_31_02AM__436__Second_Chance_Exception_C0000005.dmp   当线程26发生访问冲突异常(0xC0000005)时   另一个模块试图调用以下卸载的模块:   WMNetMgr.dll>

     

主题26:   调用堆栈Unloaded_WMNetMgr.dll + cf5cf   0x000000de 575cf7c0 0x000000dc 2ed5ec10

这是此调试器报告的唯一错误。报告中的.NET堆栈跟踪中没有其他例外。我似乎无法获得此特定.dll的调试符号,这些消息似乎没有用处。

该应用程序利用WMPLib在wmplayer启动时创建单例实例,以通过来自客户端的Web请求在Windows Server 2012实例上播放声音。该应用程序在这方面起作用,没有问题播放声音和来自多个用户的请求。

这是Singleton:

public sealed class SoundboardSingleton : IDisposable
{
    private static readonly Lazy<SoundboardSingleton> lazy =
        new Lazy<SoundboardSingleton>(() => new SoundboardSingleton());

    public static SoundboardSingleton Instance { get { return lazy.Value; } }

    public WindowsMediaPlayer WMPlayer;
    private StayAliveBot _liveBot;
    private Thread _botThread;

    private SoundboardSingleton()
    {
        WMPlayer = new WindowsMediaPlayer();
        WMPlayer.settings.volume = 50;

        _liveBot = new StayAliveBot();
        _botThread = new Thread(_liveBot.Live);
        _botThread.Start();
    }

    public void Dispose()
    {
        if (_botThread.IsAlive)
        {
            _botThread.Abort();
        }
    }
}

public class StayAliveBot
{
    public void Live()
    {
        while (SoundboardSingleton.Instance != null)
        {
            Thread.Sleep(1500000);
            SoundboardHelper.PlaySound("C:\\SoundboardOpFiles\\TestTone.wav");
        }
    }
}

最初通过以下方式在Startup.cs中实例化:

public partial class Startup
{
    public void Configuration(IAppBuilder app)
    {
        ConfigureAuth(app);

        // startup soundboard singleton
        SoundboardSingleton.Instance.WMPlayer.settings.volume = 50;
    }
}

我可以在我的本地开发机器上运行此应用程序,没有任何问题或崩溃。一切都按预期运行,没有崩溃。在部署到EC2实例时,站点上的所有内容都能正常运行,但现在每15分钟就会崩溃/重置一次。

我怀疑是:

A)WMPLib实例存在问题,并且在Windows Server 2012框中缺少一些依赖性,允许它播放声音,但会导致定期崩溃。

B)我在单例实例化时犯了一个错误,它以某种方式崩溃了我的应用程序。

我尝试过解决方案here,但没有结果。

任何帮助都将不胜感激。

编辑:我已经确认该问题与WMPLib的使用有关,因为删除它的使用每15分钟就会停止崩溃。仍然不确定为什么会这样。

2 个答案:

答案 0 :(得分:2)

这不是对你的问题的直接回答,而是对同一件事采用不同的方式。而不是WMPLib COM控件,尝试使用WPF中的线程安全的MediaPlayer类。添加对WindowsBase和PresentationCore的引用,并使用类似的东西:

using System.Windows.Media;

public void PlaySound(string filename)
{
    var mplayer = new MediaPlayer();
    mplayer.MediaEnded += new EventHandler(MediaEndedHandler);
    mplayer.Open(new Uri(filename));
    mplayer.Play();
}

public void MediaEndedHandler(object sender, EventArgs e)
{
    ((MediaPlayer)sender).Close();
}

您也可以像上面一样使用它作为单例,并且它是完全线程安全的,而WMPLib则不是。

Documentation here

修改

如评论中所述,您实际上只能使用具有公共bool属性的静态类来显示忙碌信号。 IIS中的静态类在应用程序的所有请求之间共享,并且该类仅在应用程序池被回收时进行垃圾收集,因此您需要小心存储在其中的对象的生命周期,以避免内存消费问题。此代码将为每个PlaySound()使用媒体播放器类的新实例,并在完成播放后立即将其处理掉,但忙标志在对服务器发出的所有请求中都很常见。

using System;
using System.Threading;
using System.Windows.Media;

namespace SoundBoardApp
{
    public static class Soundboard
    {
        private static bool _isBusy = false;

        public static bool IsBusy { get { return _isBusy; } }

        private static void MediaEndedHandler(object sender, EventArgs e)
        {
            _isBusy = false;
            var wmp = ((MediaPlayer)sender);
            wmp.MediaEnded -= new EventHandler(MediaEndedHandler);
            wmp.Close();            
        }

        public static bool PlaySound(string filename)
        {
            if (!_isBusy)
            {
                _isBusy = true;
                var wmp = new MediaPlayer();
                wmp.MediaEnded += new EventHandler(MediaEndedHandler);
                wmp.Volume = 0.5;
                wmp.Open(new Uri(filename));
                wmp.Play();
                return true;
            }
            else
            {
                return false;
            }            
        }

    }

    public class StayAliveBot
    {
        public void Live()
        {
            while (true)
            {
                Thread.Sleep(1500000);
                if (!Soundboard.IsBusy) Soundboard.PlaySound("C:\\SoundboardOpFiles\\TestTone.wav");
            }
        }
    }
}

答案 1 :(得分:1)

我最终使用NAudio和我的单身模式。

根据Lex Li的建议,我使用第三方作为Windows.MediaPlayer不适用于网络应用程序。根据Drunken Code Monkey的解决方案,我在单例上使用布尔标志来评估由单独的线程经常检查的播放状态,该线程评估我的单例中IWavePlayer对象上的PlaybackState.Stopped值。我唯一担心的是表现。我还没有注意到任何问题,但我非常确定如果可以通过网络应用程序进行操作,那么在Handler中管理事件会更高效,代码更少。< / p>

以下是代码:

using NAudio.Wave;

public sealed class SoundboardSingleton : IDisposable
{
    private static readonly Lazy<SoundboardSingleton> lazy =
        new Lazy<SoundboardSingleton>(() => new SoundboardSingleton());

    public static SoundboardSingleton Instance { get { return lazy.Value; } }

    public IWavePlayer WaveOutDevice { get; set; }
    public AudioFileReader AudioFileReaderObj { get; set; }
    public float Volume { get; set; }

    private MediaCloser _mediaCloser;
    private Thread _mediaCloserThread;
    private StayAliveBot _liveBot;
    private Thread _botThread;

    public bool IsBusy { get; set; }

    private SoundboardSingleton()
    {
        // checks our NAudio WaveOutDevice playback for stop
        _mediaCloser = new MediaCloser();
        _mediaCloserThread = new Thread(_mediaCloser.CheckForStoppedPlayback);
        _mediaCloserThread.Start();

        // thread to play sound every 25 minutes, to avoid idle flag
        _liveBot = new StayAliveBot();
        _botThread = new Thread(_liveBot.Live);
        _botThread.Start();
    }

    public bool PlaySound(string filename)
    {
        // make sure we are not active
        if (IsBusy) { return false; }

        // process sound
        IsBusy = true;
        WaveOutDevice = new WaveOutEvent();
        AudioFileReaderObj = new AudioFileReader(filename);
        AudioFileReaderObj.Volume = Volume;
        WaveOutDevice.Init(AudioFileReaderObj);
        WaveOutDevice.Play();

        return true;
    }

    public void CloseWaveOut()
    {
        // clean up sound objects
        WaveOutDevice?.Stop();

        if (AudioFileReaderObj != null)
        {
            AudioFileReaderObj.Dispose();
            AudioFileReaderObj = null;
        }
        if (WaveOutDevice != null)
        {
            WaveOutDevice.Dispose();
            WaveOutDevice = null;
        }
    }

    public void Dispose()
    {
        if (_mediaCloserThread.IsAlive)
        {
            _mediaCloserThread.Abort();
        }
        if (_botThread.IsAlive)
        {
            _botThread.Abort();
        }
    }
}

public class MediaCloser
{
    public void CheckForStoppedPlayback()
    {
        while (true)
        {
            // continuously check for our stopped playback state to cleanup
            Thread.Sleep(500);
            if (SoundboardSingleton.Instance.WaveOutDevice != null &&
                SoundboardSingleton.Instance.WaveOutDevice.PlaybackState == PlaybackState.Stopped)
            {
                SoundboardSingleton.Instance.CloseWaveOut();
                SoundboardSingleton.Instance.IsBusy = false;
            }
        }
    }
}

public class StayAliveBot
{
    public void Live()
    {
        while (true)
        {
            // prevent bot from going idle
            Thread.Sleep(1500000);
            if (!SoundboardSingleton.Instance.IsBusy)
            {
                SoundboardSingleton.Instance.PlaySound(ConfigurationManager.AppSettings["SoundboardHeartbeatFile"]);
            }
        }
    }
}

希望这可以帮助任何人遇到同样的问题。我的网站已经启动并运行了几个小时,没有任何问题,客户向董事会发送垃圾邮件。再次感谢所有帮助过的人。