How to implement a 'wait' state when using

2019-08-16 19:27发布

问题:

I have a class that I am applying multi-threading to. I would like to only allow 1 thread to 'startSpeaking()' at one time. Here is my attempt:

class VoiceEffect
{
    SpeechSynthesizer reader = new SpeechSynthesizer();

    static readonly object _locker = new object();

    public void createVoiceThread(string str)
    {
        Thread voicethread = new Thread(() => startSpeaking(str)); // Lambda Process
        voicethread.IsBackground = true;
        voicethread.Start();
    }
    public void startSpeaking(string str)
    {
        lock (_locker)
        {
            reader.Rate = -2; // Voice  effects.
            reader.Volume = 100;
            reader.Speak(str);
        }
    }
}

I am also calling createVoiceThread() method from another class. It is called by a similar convention in another class. E.g.

class Program
{
    static void Main(string[] args)
    {
        VoiceEffect ve = new VoiceEffect();
        string text = "Hello world, how are you today? I am super-duper!!";

       for( int i=0 ; i < 10 ; i++ )
       {
          ve.createVoiceThread(text);
          ve.startSpeaking(text);

          Thread.Sleep(1000);
       }
    }
}

My question is how can I modify this program so that when startSpeaking() is called by any thread, that it only plays a single speech pattern at a time.

回答1:

I know this question's old as hell, but if I'm understanding your question correctly (that you want all the speech done sequentially, as if it were done on a single thread) you can do something like this:

static class VoiceEffect
{
    SpeechSynthesizer reader = new SpeechSynthesizer();
    private volatile bool _isCurrentlySpeaking = false;

    /// <summary>Event handler. Fired when the SpeechSynthesizer object starts speaking asynchronously.</summary>
    private void StartedSpeaking(object sender, SpeakStartedEventArgs e)
    { _isCurrentlySpeaking = true; }
    /// <summary>Event handler. Fired when the SpeechSynthesizer object finishes speaking asynchronously.</summary>
    private void FinishedSpeaking(object sender, SpeakCompletedEventArgs e)
    { _isCurrentlySpeaking = false; }

    private VoiceEffect _instance;
    /// <summary>Gets the singleton instance of the VoiceEffect class.</summary>
    /// <returns>A unique shared instance of the VoiceEffect class.</returns>
    public VoiceEffect GetInstance()
    {
        if(_instance == null)
        { _instance = new VoiceEffect(); }
        return _instance;
    }

    /// <summary>
    /// Constructor. Initializes the class assigning event handlers for the
    /// SpeechSynthesizer object.
    /// </summary>
    private VoiceEffect()
    {
        reader.SpeakStarted += new EventHandler<SpeakStartedEventArgs>(StartedSpeaking);
        reader.SpeakCompleted += new EventHandler<SpeakCompletedEventArgs>(FinishedSpeaking);
    }

    /// <summary>Speaks stuff.</summary>
    /// <param name="str">The stuff to speak.</param>
    public void startSpeaking(string str)
    {
        reader.Rate = -2; // Voice  effects.
        reader.Volume = 100;

        // if the reader's currently speaking anything,
        // don't let any incoming prompts overlap
        while(_isCurrentlySpeaking)
        { continue; }

        reader.SpeakAsync(str);
    }

    /// <summary>Creates a new thread to speak stuff into.</summary>
    /// <param name="str">The stuff to read.</param>
    public void createVoiceThread(string str)
    {
        Thread voicethread = new Thread(() => startSpeaking(str)); // Lambda Process
        voicethread.IsBackground = true;
        voicethread.Start();
    }        
}

This gives you a singleton class that will manage all threads, and all threads will share the _isCurrentlySpeaking variable, which will mean that no speech prompts will ever overlap each other since they'll all have to wait until the variable is cleared before speaking. What I cannot guarantee is the order the prompts will be read (i.e., take control of the message-processing queue), if you submit multiple prompts to the queue while there's a prompt being spoken aloud already. Either way, this should pretty much work.



回答2:

Your question isn't clear, but you have a single lock variable (_locker) which is static - that means only one thread can ever be executing startSpeaking at a time. It's not clear whether you're trying to make threads wait for each other, or whether your question is because you don't want them to wait for each other.

Either way, having a single static lock used like this is distinctly dubious, IMO. If you can really only effectively have one useful instance of this class, consider making it a singleton. (Generally not nice in terms of design.) If it's fine to have multiple independent instances, then make them independent by making the _locker variable an instance variable.

(I'd also strongly advise you to start following .NET naming conventions.)