【问题标题】:System.Speech.Synthesis hangs with high CPU on 2012 R2System.Speech.Synthesis 在 2012 R2 上以高 CPU 挂起
【发布时间】:2015-11-21 02:25:15
【问题描述】:

我有一个 asp.net MVC 应用程序,它有一个控制器操作,它将字符串作为输入并发送合成语音的响应 wav 文件。这是一个简化的例子:

    public async Task<ActionResult> Speak(string text)
    {
        Task<FileContentResult> task = Task.Run(() =>
        {
            using (var synth = new System.Speech.Synthesis.SpeechSynthesizer())
            using (var stream = new MemoryStream())
            {
                synth.SetOutputToWaveStream(stream);
                synth.Speak(text);
                var bytes = stream.GetBuffer();
                return File(bytes, "audio/x-wav");
            }
        });
        return await task;
    }

应用程序(尤其是此操作方法)在 2008 R2 服务器、2012(非 R2)服务器和我的 8.1 开发 PC 上的服务器环境中运行良好。它还在标准的 Azure 2012 R2 虚拟机上运行良好。但是,当我将它部署到三台 2012 R2 服务器(它最终的永久主页)时,操作方法永远不会产生 HTTP 响应——IIS Worker 进程无限期地最大化 CPU 内核之一。使用 Procmon 观看服务器时,事件查看器中没有任何内容,也没有任何内容出现在我身上。我已经通过远程调试附加到该进程,并且synth.Speak(text) 永远不会返回。执行synth.Speak(text) 调用时,我立即在服务器的任务管理器中看到失控的 w3wp.exe 进程。

我的第一个倾向是相信某些进程通常会干扰服务器上的语音合成,但 Windows Narrator 可以正常工作,并且像这样的简单控制台应用程序也可以正常工作:

static void Main(string[] args)
{
    var synth = new System.Speech.Synthesis.SpeechSynthesizer();
    synth.Speak("hello");
}

所以很明显我不能责怪服务器的语音合成。所以也许我的代码有问题,或者 IIS 配置有什么奇怪的地方?如何使此控制器操作在这些服务器上正常工作?

这是一种测试操作方法的简单方法(只需为路由获取正确的url 值):

<div>
    <input type="text" id="txt" autofocus />
    <button type="button" id="btn">Speak</button>
</div>

<script>
    document.getElementById('btn').addEventListener('click', function () {
        var text = document.getElementById('txt').value;
        var url = window.location.href + '/speak?text=' + encodeURIComponent(text);
        var audio = document.createElement('audio');
        var canPlayWavFileInAudioElement = audio.canPlayType('audio/wav'); 
        var bgSound = document.createElement('bgsound');
        bgSound.src = url;
        var canPlayBgSoundElement = bgSound.getAttribute('src');

        if (canPlayWavFileInAudioElement) {
            // probably Firefox and Chrome
            audio.setAttribute('src', url);
            audio.setAttribute('autoplay', '');
            document.getElementsByTagName('body')[0].appendChild(audio);
        } else if (canPlayBgSoundElement) {
            // internet explorer
            document.getElementsByTagName('body')[0].appendChild(bgSound);
        } else {
            alert('This browser probably can\'t play a wav file');
        }
    });
</script>

【问题讨论】:

  • 您是否尝试过使其成为同步操作方法,而不将其包装在任务中?该代码中的线程池可能存在 ASP .NET 不知道的问题。
  • @DmitryS。我不确定我是否可以进行这样的测试...如果未包装synth.Speak,我会收到运行时异常:InvalidOperationException(此时无法启动异步操作)。我喜欢这种思路,但如果这是 ASP.NET 的线程池问题,为什么它会在这么多其他服务器上工作?
  • 最新版合成器有SpeakAsync()方法。不用将代码包装在Task.Run() 中,您只需执行await SpeakAsync(text)msdn.microsoft.com/en-us/library/…
  • 也许我在这里遗漏了一些东西 - SpeakAsync() 方法不会返回任务,因此无法等待。 (它返回一个Prompt 对象)。
  • 你尝试过这里的建议吗:peterluzc.blogspot.ru/2014/01/…?我知道这是关于异常而不是挂起,但仍然可能相关。

标签: c# asp.net-mvc text-to-speech windows-server-2012-r2 speech-synthesis


【解决方案1】:

我认为问题在于返回类型。 IIS Express 可以让你侥幸逃脱,但 IIS 不是:

Task<FileContentResult>

所以如果你尝试:

public async Task<FileContentResult> Speak(string text)
{
    Task<FileContentResult> task = Task.Run(() =>
    {
        using (var synth = new System.Speech.Synthesis.SpeechSynthesizer())
        using (var stream = new MemoryStream())
        {
            synth.SetOutputToWaveStream(stream);
            synth.Speak(text);
            var bytes = stream.GetBuffer();
            return File(bytes, "audio/x-wav");
        }
    });
    return await task;
}

我敢打赌,您还需要在 IIS 中添加音频/wav MIME 类型。

【讨论】:

  • 很遗憾,不,这不会改变 2012 R2 的行为。
  • 否,已在 IIS 中配置 wav MIME 类型。但无论如何,这些都是针对静态文件的。
  • 所以ISAPI过滤器可能需要正确配置?
  • 不,这不是 ISAPI 过滤器。
  • 哦,我刚刚想到了一些事情。确保在构建 Artifact 以放置在 2012 R2 服务器上时,确保 System.Speech Reference 设置为 Copy Always(如果尚未设置)。然后(之前应该想到这一点)在任务主体周围放置一个 try catch ,看看它是否抛出,如果你还没有抛出的话。
【解决方案2】:

这只是我的想法,尚未经过测试,但您可以执行以下操作:

public ActionResult Speak(string text)
{
var speech = new SpeechSynthesizer();
speech.Speak(text);

byte[] bytes;
using (var stream = new MemoryStream())
{
    speech.SetOutputToWaveStream(stream);
    bytes = stream.ToArray();
}
return File(bytes, "audio/x-wav");
}

【讨论】:

    【解决方案3】:

    我之前有过使用服务器 2012R2 的经验(不是授予的合成器 api,但同样的问题)。我通过在所有任务上使用“await task.ConfigureAwait(false)”来修复它。看看这是否适合你。

    祝你好运。

    【讨论】:

    • 谢谢,但很遗憾,不,这无助于解决这个特定问题。
    【解决方案4】:

    this blog,您可以找到类似问题的解决方案 - 在全新安装的 Windows 8.1 上使用 SpeechSynthesizer 时出现异常。这种情况下的问题是 CurrentUserLexicon 用户的权限条目错误(由 SpeechSynthesizer 使用。要解决此问题,此博客文章建议从 Software\Microsoft\Speech\CurrentUserLexicon 注册表项中删除权限条目“ALL APPLICATION PACKAGES”。

    【讨论】:

      【解决方案5】:

      我发现我可以在其他服务器上重现该问题,包括 Azure VM,因此我排除了我们特定环境出现问题的可能性。

      另外,我发现如果我以服务器上的管理员身份运行应用程序池并且之前已登录到服务器,我可以让代码在 2012 R2 上正常工作。在排除权限问题的漫长过程之后,我决定一定是登录过程中发生的某些事情使 TTS API 调用能够正常工作。 (无论是什么,我都无法通过 procmon 跟踪找到它)。幸运的是,ApplicationPoolIdentity 可以通过在 IIS 中打开应用程序池的“高级设置”并将Load User Profile 设置为True 来应用类似的登录魔法。

      运行应用程序池的身份还需要读取HKU\.Default\Software\Microsoft\Speech 的权限,可以通过使用本地服务器获取位置和IIS APPPOOL\.Net v4.5 用户名(其中.Net v4.5 是应用程序的名称)授予ApplicationPoolIdentity池)。

      一旦授予对 reg 键的读取权限,并且应用程序池配置为加载用户配置文件,上述代码就可以正常工作。在 Azure VM 和来自 MSDN ISO 的 vanilla 2012 R2 上进行了测试。

      【讨论】:

      • 伙计,你是英雄!这绝对是我的一周。非常感谢! - 顺便说一句,iisreset 是我让它生效所必需的。
      猜你喜欢
      • 1970-01-01
      • 2018-12-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多