System.Speech.Synthesis зависает с высокой загрузкой процессора на 2012 R2

У меня есть приложение MVC asp.net, которое имеет действие контроллера, который принимает строку в качестве входных данных и отправляет 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 dev. Он также работает нормально на стандартной виртуальной машине Azure 2012 R2. Однако при развертывании его на трех серверах 2012 R2 (его возможный постоянный дом) метод действия никогда не выдает ответ HTTP - рабочий процесс IIS максимально увеличивает одно из ядер ЦП. В средстве просмотра событий ничего нет и ничего не выскакивает у меня при просмотре сервера с 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>

5 ответов

Решение

Я обнаружил, что могу воспроизвести проблему на других серверах, включая виртуальные машины Azure, поэтому исключил возможность возникновения проблемы с нашей конкретной средой.

Кроме того, я обнаружил, что мог бы заставить код работать нормально на 2012 R2, если бы запускал пул приложений под учетной записью администратора на сервере и ранее входил на сервер. После очень длительного процесса исключения проблем с разрешениями я решил, что в процессе входа в систему должно быть что-то, что позволяет правильно выполнять вызовы API TTS. (Что бы это ни было, я не смог найти его, копающегося в следах отростка). К счастью, к ApplicationPoolIdentity можно применить аналогичную магию входа, открыв "Расширенные настройки" для пула приложений в IIS и установив Load User Profile в True,

Идентификатор, который запускает пул приложений, также должен иметь разрешение на чтение. HKU\.Default\Software\Microsoft\Speech который может быть предоставлен ApplicationPoolIdentity с использованием локального сервера для местоположения и IIS APPPOOL\.Net v4.5 для имени пользователя (где .Net v4.5 имя пула приложений).

После предоставления разрешения на чтение ключа reg и настройки пула приложений для загрузки профиля пользователя приведенный выше код работает нормально. Протестировано на виртуальных машинах Azure и vanilla 2012 R2 из ISO-образов MSDN.

Я думаю, что проблема заключается в типе возврата. 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;
}

Бьюсь об заклад, вы также должны добавить аудио / WAV MIME Type в IIS.

У меня был этот опыт с сервером 2012R2 раньше (не предоставленный синтезатор API, но та же проблема). Я исправил это с помощью "await task.ConfigureAwait(false)" для всех моих задач. Посмотрите, работает ли это для вас.

Удачи.

В этом блоге вы можете найти решение аналогичной проблемы - исключение при использовании SpeechSynthesizer на новой установке Windows 8.1. Проблема в этом случае заключается в неправильной записи разрешения для пользователя CurrentUserLexicon (который используется SpeechSynthesizer. Для устранения этой записи в блоге предлагается удалить запись разрешения "ВСЕ ПАКЕТЫ ПРИЛОЖЕНИЙ" из раздела реестра Software\Microsoft\Speech\CurrentUserLexicon).

Это просто не в моей голове, и это не было проверено, но вы можете сделать что-то вроде этого:

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");
}
Другие вопросы по тегам