winforms 语音合成器“SpeakAsyncCancelAll”在用户界面线程中运行

hmmo2u0o  于 2023-01-09  发布在  其他
关注(0)|答案(3)|浏览(202)

我有一个Windows窗体应用程序,我试图添加辅助功能,但遇到了语音合成器的问题,似乎SpeechAsyncCancelAll在用户界面线程中运行。性能完全取决于PC的能力。这可以通过Windows窗体中的一个非常简单的应用程序重现。创建一个窗体并添加一个数字上下控件。然后使用以下代码:

using System.Windows.Forms;
using System.Speech;
using System.Speech.Synthesis;

namespace WindowsFormsApp8
{
    public partial class Form1 : Form
    {
        SpeechSynthesizer _speech = new SpeechSynthesizer();
        public Form1()
        {
            InitializeComponent();
        }

        private void numericUpDown1_ValueChanged(object sender, EventArgs e)
        {
            _speech.SpeakAsyncCancelAll();
            _speech.SpeakAsync(numericUpDown1.Value.ToString());
        }
    }
}

在我的开发机器上,这是非常强大的,它运行没有问题,非常快,当你按下向上箭头。每个值被取消,所以你听不到任何控制增量,当你停止按下向上箭头,它宣布最后一个值正确。然而,这是在一个较小的PC上运行的分钟,甚至核心i9 hexacore机器,增量上的重复慢得像爬行一样。2在我看来这是在用户界面线程上运行的。3有什么建议吗?4谢谢

aor9mmx1

aor9mmx11#

不要被SpeakAsyncCancelAll()方法名中的“Async”所欺骗。正如在SpeechSynthesizerVoiceSynthesis类的源代码中所看到的,为了与执行实际语音合成的后台线程进行通信,涉及了相当多的同步代码。由于使用了多个lock语句,因此这些代码实际上相当繁重。
对于这种情况(多个连续的用户交互可能会创建一系列代码React,但最终我们只需要最后一个),最佳实践解决方案是不直接启动React,而是启动计时器,并仅在同时没有其他用户交互的情况下执行React。

public partial class Form1 : Form
{
    private SpeechSynthesizer _speech = new SpeechSynthesizer();

    public Form1()
    {
        InitializeComponent();
        timer1.Interval = 500;
    }

    private void numericUpDown1_ValueChanged(object sender, EventArgs e)
    {
        // Reset timer
        timer1.Stop();
        timer1.Start();
    }

    private void timer1_Tick(object sender, EventArgs e)
    {
        timer1.Stop();

        _speech.SpeakAsyncCancelAll();
        _speech.SpeakAsync(numericUpDown1.Value.ToString());
    }
}

您应该允许用户配置计时器间隔,以便根据其系统性能和个人使用模式选择一个合适的折衷方案。需要音频帮助的人通常会出于充分的理由认为用户活动和音频响应之间的延迟太长是在浪费时间。因此,用户可以配置这样的延迟以最好地满足其个人需求,这一点非常重要。

dgtucam1

dgtucam12#

让我们假设您已经考虑了Neil的精彩评论,并且在其他PC上检查了NumericUpDown控件的重复率,而“没有”调用语音引擎。
您的代码看起来是正确的。SpeakAsyncCancelAllSpeakAsync没有阻塞,并且“预期”在后台线程上运行。(不令人震惊)使用你描述的测试条件,你的代码在我的PC上工作得很好。也许你可以尝试几个变化的微小机会,一些东西使不同,并产生某种线索,通过排除一些不太可能的问题。

变化1

使用BeginInvoke捕获“要说的文本”并发布工作,这样可以确保ValueChangedMouseDown消息不会受到消息队列中的干扰。

private void numericUpDown1_ValueChanged(object sender, EventArgs e)
{
    // Make 100% sure that the up-down ctrl is decoupled from speak call.
    var say = $"{numericUpDown1.Value}";

    // Ruling out an unlikely problem
    BeginInvoke((MethodInvoker)delegate
    {
        _speech.SpeakAsyncCancelAll();
        _speech.SpeakAsync(say);
    });
}

变化2

既然你怀疑UI线程上运行了一些不应该运行的东西,那么就直接给予明确的指示,把它发布到后台任务中,至少我们可以排除这种情况。

private void numericUpDown2_ValueChanged(object sender, EventArgs e)
{
    // Make 100% sure that the up-down ctrl is decoupled from speak call.
    var say = $"{numericUpDown2.Value}";

    // Ruling out an unlikely problem
    Task.Run(() =>
    {
        _speech.SpeakAsyncCancelAll();
        _speech.SpeakAsync(say);
    });
}

变体3 -灵感来自NineBerry的answer(添加到测试代码项目repo)

/// <summary>
/// Watchdog timer inspired by NineBerry.
/// https://stackoverflow.com/a/74975629/5438626
/// Please accept THAT answer if this solves your issue.
/// </summary>
int _changeCount = 0;
private void numericUpDown3_ValueChanged(object sender, EventArgs e)
{
    var captureCount = ++_changeCount;
    var say = $"{numericUpDown3.Value}";
    Task
        .Delay(TimeSpan.FromMilliseconds(250))
        .GetAwaiter()
        .OnCompleted(() => 
        {
            if(captureCount.Equals(_changeCount))
            {
                Debug.WriteLine(say);
                _speech.SpeakAsyncCancelAll();
                _speech.SpeakAsync(say);
            }
        });
}
j2datikz

j2datikz3#

以上答案并不能解决问题。但是,所有测试的计算机都是戴尔计算机。默认情况下,当安装操作系统时,戴尔会安装一个名为MaxWaves的声音实用程序,它允许不同的音频增强。虽然此实用程序中的所有选项都处于关闭状态,但它似乎会缓冲声音,并且当异步。全部取消时()来电,它会阻塞直到声音持续时间结束。2因此一切看起来都慢得像爬行。3卸载这个实用程序以及禁用它作为一个服务纠正了这个问题。4现在一切都正常工作。5谢谢你的回答。

相关问题