System.Speech.Synthesis在2012 R2上挂起了高CPU

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

public async Task Speak(string text) { Task 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 ​​dev PC上的服务器环境中运行良好。 它在标准的Azure 2012 R2虚拟机上运行良好。 但是,当我将其部署到三个2012 R2服务器(最终永久主页)时,操作方法永远不会产生HTTP响应 – IIS工作进程无限期地最大化其中一个CPU核心。 事件查看器中没有任何内容,在使用Procmon查看服务器时没有任何内容跳出来。 我已经通过远程调试附加到进程,并且synth.Speak(text)永远不会返回。 当执行synth.Speak(text)调用时,我立即在服务器的任务管理器中看到失控的w3wp.exe进程。

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

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

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

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

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

我认为问题是返回类型。 IIS Express让你逃脱它,但IIS不是:

 Task 

所以,如果你尝试:

 public async Task Speak(string text) { Task 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中添加audio / wav MIME Type。

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

祝好运。

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

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

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

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

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

这只是我的头脑,并没有经过测试,但你可以做这样的事情:

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