Подтвердить что ты не робот

System.Speech.Synthesis зависает с высоким процессором в 2012 R2

У меня есть приложение 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 dev ПК. Он также отлично работает на стандартной виртуальной машине Azure 2012 R2. Однако, когда я развертываю его на трех серверах R2 R2 R2 (его возможном постоянном доме), метод действий никогда не вызывает ответа HTTP - процесс IIS Worker неограниченно увеличивает один из ядер процессора. В телезрителе событий ничего нет, и при просмотре сервера с Procmon ничего не выпрыгивает. Я подключился к процессу с удаленной отладкой, а synth.Speak(text) никогда не возвращается. Когда выполняется вызов synth.Speak(text), я сразу вижу процесс runaway w3wp.exe в диспетчере задач сервера.

Мое первое желание состояло в том, чтобы полагать, что какой-то процесс мешал синтезу речи вообще на серверах, но диспетчер Windows работает правильно, и простое консольное приложение, подобное этому, также работает правильно:

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>
4b9b3361

Ответ 1

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

Кроме того, я обнаружил, что я могу заставить код работать нормально на 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 VM и vanilla 2012 R2 из ISO-стандартов MSDN.

Ответ 2

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

Ответ 3

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

Удачи.

Ответ 4

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

Ответ 5

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

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