0

我的 Web 应用程序经常崩溃并重置 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 错误偏移量:0x00 错误进程 000000000cfd50cfid:应用程序启动时间: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 - 为应用程序池“SiteName”提供服务的进程与 Windows 进程激活服务发生了致命的通信错误。进程 ID 为“6096”。数据字段包含错误号。

接下来我运行了 DebugDiag2 收集和分析:

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

线程 26:调用堆栈 Unloaded_WMNetMgr.dll+cf5cf 0x000000de 575cf7c0 0x000000dc2ed5ec10

这是此调试器报告的唯一错误。报告上的 .NET 堆栈跟踪中没有其他异常。我似乎无法获得这个特定 .dll 的调试符号,而且这些消息似乎也不是很有帮助。

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

这是单例:

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)我的单例实例化出错了,它以某种方式使我的应用程序崩溃。

我在这里尝试了解决方案,但没有结果。

任何帮助,将不胜感激。

编辑:我已经确认这个问题与 WMPLib 的使用有关,因为删除它的使用会阻止每 15 分钟崩溃一次。仍然不确定为什么会发生这种情况。

4

2 回答 2

2

这不是对您的问题的直接回答,而是对同一件事的不同处理方式。尝试使用 WPF 中的线程安全 MediaPlayer 类,而不是 WMPLib COM 控件。添加对 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 不是。

文档在这里

编辑:

如评论中所述,您确实可以仅使用具有公共 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");
            }
        }
    }
}
于 2017-09-20T23:53:40.613 回答
1

我最终将NAudio与我的单例模式一起使用。

根据 Lex Li 的建议,我使用了第三方,因为 Windows.MediaPlayer 不适用于 Web 应用程序。基于 Drunken Code Monkey 的解决方案,我在单例中使用了一个布尔标志来评估播放状态,该状态由一个单独的线程频繁检查,该线程评估我的单例中 IWavePlayer 对象上的 PlaybackState.Stopped 值。我唯一关心的是性能。我还没有注意到任何问题,但我敢肯定,如果甚至可以从 Web 应用程序中执行,在 Handler 中管理事件会更高效且代码更少。

这是代码:

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"]);
            }
        }
    }
}

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

于 2017-09-21T22:22:12.177 回答