none
How to monopolize a thread? RRS feed

  • Question

  • I've got this static class which I use for various announcements. It is very useful.

    using System;
    using SpeechLib;
    using System.Threading;

    namespace TapTcp_1
    {
        static class TSP
        {
            static object obj = new object ( );
            static string textToSpeakDelegate = String.Empty;
            public static void TextToSpeech ( string text )
            {
                if ( Globals.blockTSP )
                {
                    return;
                }
                for ( int jj = 0; jj < Globals.generListThreads.Count; jj++ )
                {
                    if ( Globals.generListThreads[ jj ] != null )
                    {
                        if ( Globals.generListThreads[ jj ].Name == "tsp" )
                        {
                            if ( Globals.generListThreads[ jj ].IsAlive )
                            {
                                return;
                            }
                        }
                    }
                }
                TSP.textToSpeakDelegate = text;
                ThreadStart threadStart = new ThreadStart ( textToSpeech2 );
                Thread trd = new Thread ( threadStart );
                trd.Name = "tsp";
                trd.Start ( );
                TSP.obj = trd;
                System.Threading.Monitor.Enter ( trd );
            }                                               // TextToSpeech

            private static void textToSpeech2 ( )
            {
                Globals.blockTSP = true;
                SpVoice voice = new SpVoice ( );
                voice.Speak ( TSP.textToSpeakDelegate, SpeechVoiceSpeakFlags.SVSFlagsAsync );
                voice.WaitUntilDone ( 30000 );
                Globals.blockTSP = false;
         //       System.Threading.Monitor.Exit ( ( Thread )TSP.obj );
            }                                               // textToSpeech2
        }
    }

    I've used it for years but yesterday I noticed a problem.Sometimes the demand on the class is very strong and many messages are sent to it from numerous parts of a large application. Messages become gabled. Sometimes I hear fragments of phrases. Worse still, each call generates a new thread and eventually the whole application and even the whole machine sort of come to a standstill.

    After it came to my attention I introduced a global variable Globals.blockTSP in textToSpeech2 method. It works now fine. Then I began to look further. It crossed my mind that under the circumstances it would be best to make sure every message is executed in the same thread. I am not sure if the currently active user thread will be destroyed after the method is done. Perhaps it will linger and overhead will still accumulate.

    How can I do it: to make sure this class uses always the same thread for its work? Is it possible?

    A somewhat related question concerns Monitor class. I have this Monitor.Enter statement but could not put Monitor.Exit because of an exception. I commented the offesive statement out. The exceptionw is that I tried to use Monitor from a non-monitored block of code. How can I set the Exit strategy up for this case? Is it necessery?

    Thanks.

     


    AlexB - Win_7 Pro64, SqlSer64 WinSer64
    Saturday, June 12, 2010 5:32 PM

All replies

  • Sometimes simply posting helps to resolve an issue. The second problem was easy, I permutated two statements:

    TSP.textToSpeakDelegate = text;
                ThreadStart threadStart = new ThreadStart ( textToSpeech2 );
                Thread trd = new Thread ( threadStart );
                trd.Name = "tsp";
                trd.Start ( );
                System.Threading.Monitor.Enter ( trd );
                TSP.obj = trd;
            }                                               // TextToSpeech

            private static void textToSpeech2 ( )
            {
                Globals.blockTSP = true;
                SpVoice voice = new SpVoice ( );
                voice.Speak ( TSP.textToSpeakDelegate, SpeechVoiceSpeakFlags.SVSFlagsAsync );
                voice.WaitUntilDone ( 30000 );
                Globals.blockTSP = false;
                System.Threading.Monitor.Exit ( ( Thread )TSP.obj );

    Now Monitor works. The first question still is on the table.


    AlexB - Win_7 Pro64, SqlSer64 WinSer64
    Saturday, June 12, 2010 5:53 PM
  • I jumped the gun. Alas, it does not work. Amazingly it worked only once and then I started getting the same exception:

    Message=Object synchronization method was called from an unsynchronized block of code.

    Thanks.


    AlexB - Win_7 Pro64, SqlSer64 WinSer64
    Saturday, June 12, 2010 6:22 PM
  • Hi AlexB,

    I'm not sure I understand the whole scenario, so please correct me if i'm wrong:)

    The computer can only speak one sentence at a time, I don't think it is a proper way to initiate separate thread for each text and then speak those texts in different threads, instead, we may make sure that the TextToSpeech method will be only executed by one thread at a time with a lock statement:

     

            static object obj = new object();

            public static void TextToSpeech(string text)

            {

                lock (obj)

                {

                    TSP.textToSpeakDelegate = text;

     

                    textToSpeech2();

                }

            }  


    Sincerely,
    Eric
    MSDN Subscriber Support in Forum
    If you have any feedback of our support, please contact msdnmg@microsoft.com.
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.
    Welcome to the All-In-One Code Framework! If you have any feedback, please tell us.
    Monday, June 14, 2010 7:33 AM
  • Hi Eric,

    it is not a problem. Now my computer executes one TSP call at a time because other calls are blocked. Before I introduced a global blocking boolean variables many threads were initiated and I heard gabled messages because the messages overlapped and some of them were heard only at the end. The problem is this. Once a thread is done I am afraid it will linger, thus the enxt call although not overla[[ing will generate a second TSP thread and so on. I want to prevent this and make sure only ONE thread, the same thread is used for TSP at all times.

    Thanks.


    AlexB - Win_7 Pro64, SqlSer64 WinSer64
    Monday, June 14, 2010 11:54 AM
  • It sounds like what you really want is a queue:

    using System;
    using System.Collections.Generic;
    using System.Threading;
    using SpeechLib;
    
    class Program
    {
      static void Main(string[] args)
      {
        Console.WriteLine("Press any key to continue . . .");
        TSP.TextToSpeech("Hello");
        TSP.TextToSpeech("Are we");
        TSP.TextToSpeech("There");
        TSP.TextToSpeech("yet");
        Console.ReadKey();
      }
    }
    
    static class TSP
    {
      static SpVoice voice;
      static Thread thread;
      static ManualResetEvent exitEvent;
      static AutoResetEvent queueEvent;
      static Queue<string> queue;
      static volatile int _MaxQueue;
    
      static TSP()
      {
        voice = new SpVoice();
        queue = new Queue<string>();
        _MaxQueue = 1;
        exitEvent = new ManualResetEvent(false);
        queueEvent = new AutoResetEvent(false);
        thread = new Thread(ThreadProc) { IsBackground = true };
        thread.Start();
      }
    
      public static bool DropNewest { get; set; }
    
      public static int MaxQueue
      {
        get { return _MaxQueue; }
        set
        {
          if (value <= 0)
            throw new ArgumentException("MaxQueue must be greater than zero.");
          _MaxQueue = value;
        }
      }
    
      public static void TextToSpeech(string text)
      {
        if (DropNewest)
        {
          // Ignores any new items if the queue is maxed out
          lock (queue)
          {
            if (queue.Count < _MaxQueue)
              queue.Enqueue(text);
          }
        }
        else
        {
          // Drops the oldest item in the queue if the queue is maxed out
          lock (queue)
          {
            if (queue.Count >= _MaxQueue)
              queue.Dequeue();
            queue.Enqueue(text);
          }
        }
        queueEvent.Set();
      }
    
      static void ThreadProc(object obj)
      {
        WaitHandle[] waits = new WaitHandle[] { exitEvent, queueEvent };
    
        while (WaitHandle.WaitAny(waits) > 0)
        {
          // We only get here when WaitAny returns 1 (queueEvent signaled)
          while (true)
          {
            string text = null;
            lock (queue)
            {
              if (queue.Count > 0)
                text = queue.Dequeue();
            }
            if (text == null)
              break;
            else if (text.Length > 0)
              voice.Speak(text, SpeechVoiceSpeakFlags.SVSFDefault);
          }
        }
      }
    }
    
    • Edited by Tergiver Friday, June 18, 2010 1:12 AM expanded to customize queue behavior
    Monday, June 14, 2010 2:37 PM
  • You probably want to change the Speak call to use SpeechVoiceSpeakFlags.SVSFDefault intead of Async since we are manually managing its synchronicity.
    Monday, June 14, 2010 2:41 PM
  • And since we removed the Async flag, we don't need the WaitUntilDone call (edited above).
    Monday, June 14, 2010 3:00 PM
  • A queue? I kind of thought about it but I don't think it is best for this application. I would prefer some messages to be lost actually while one of them is pronounced. Sometimes teh number of messages is very large and if I put them all in the queue I will hear them for 10 minutes. I will take a look at your code though.

    What I actually want is to have a very private thread.

    Thanks.


    AlexB - Win_7 Pro64, SqlSer64 WinSer64
    Monday, June 14, 2010 5:35 PM
  • Thank you Tergiver, I will have to look into it all later.
    AlexB - Win_7 Pro64, SqlSer64 WinSer64
    Monday, June 14, 2010 5:36 PM
  • If you want to drop some requests you could put a cap on the queue size. How you decide what to keep and what to discard is up to you.

    If you want the most recent messages, deque one and push the new one.

    You might even implement a priority queue so that you can remove a low priority message if a new message would exceed the cap.

    Monday, June 14, 2010 7:01 PM
  • It is an avenue for thought, Tergiver. I am grateful for the suggestion. I cannot digest it immediately, sorry. I may get back to it tomorrow or in a couple of days.

    As far as the decision making I rerally leave it uop  to the computer. It may sound weird but I found it the most practical way of doing things. The messages may originate in various parts of the application and sometimes they come in a way of an avalanche. This is because most of the messages are reactions to the input stream, they are sort of commentaries and alerts as to what to expect. There are also visual displays with the same meaning and there no messages are lost. So, if I lose one announcement, I don't really care, after all my perception is limited and I can do only so much. I cannot jump over my head. But I cannot stand gibberish of unfinished sentences, half words that create cacophony.

    Still you are not answering my question. I think the queue will not monopolie a thread will it?

    Thanks.


    AlexB - Win_7 Pro64, SqlSer64 WinSer64
    Tuesday, June 15, 2010 1:09 AM
  • The answer is that you can only achieve the result you desire by using another approach--which is what I gave you.

    You can only set thread affinity on a per-process basis. That will not help you.

    Tuesday, June 15, 2010 11:46 AM
  • Thanks,

    I haven't had a cjhance to try it, I had to clear otehr issues first. Now this comes to the fore. It is a overarching question of thread control. What might be the general consideration for achieving this? You said: per process. That seems to indicate that it is possible on a class level, correct? I am also thinking about another approach: forget abbout owning a thread, I can simply use a thread per call and use Abort statement to close it. Although I recall that nobugz warned about not using it. In the past it seemed to work for me well in othre circumstances. This may be as good as owning a thread, right?


    AlexB - Win_7 Pro64, SqlSer64 WinSer64

    Tuesday, June 15, 2010 1:51 PM
  • I don't know what you mean by monopolizing a thread. Tergiver gave you the correct way to do it, there's only one thread handling the speaking.
    Tuesday, June 15, 2010 6:58 PM
  • Tergiver, I appreciate your code offer but I've concluded I cannot use it. It does not fit my framework at all. I explained that I don't want to save every call or rather make sure evry class call goes thru. I would prefer a contolled logjam. This is what I did. I simplified the class, removed code I long abandoned (it was a holdover os another idea) and introduced a Join statement. Now the whole thing seems to work OK except one thing. Join statement holds execution in the main thread. In short when the voice TSP thread is active, other actions are delayed. I kind of don't like it.

    I will keep thinking about it all. If you have any idea how to resolve this new problem, please contribute.

        static class TSP
        {
            static string textToSpeakDelegate = String.Empty;
            public static void TextToSpeech ( string text )
            {
                if ( Globals.blockTSP )
                {
                    return;
                }
                TSP.textToSpeakDelegate = text;
                ThreadStart threadStart = new ThreadStart ( textToSpeech2 );
                Thread trd = new Thread ( threadStart );
                trd.Name = "tsp";
                trd.IsBackground = true;
                trd.Start ( );
                trd.Join ( );
            }                                               // TextToSpeech

            private static void textToSpeech2 ( )
            {
                Globals.blockTSP = true;
                SpVoice voice = new SpVoice ( );
                voice.Speak ( TSP.textToSpeakDelegate, SpeechVoiceSpeakFlags.SVSFlagsAsync );
                voice.WaitUntilDone ( 30000 );
                Globals.blockTSP = false;
            }                                               // textToSpeech2
        }

    Thanks.


    AlexB - Win_7 Pro64, SqlSer64 WinSer64
    Tuesday, June 15, 2010 7:04 PM
  • Alex, use Tergiver's code. If you want, change the queue method to this so it never adds up:

    public static void TextToSpeech(string text)
    {
      lock (queue)
        if (queue.Count < 3)
        {
          queue.Enqueue(text);
          queueEvent.Set();
        }
    }

    They'll never pile up. And they'll be chosen fairly randomly, since you seem to prefer that.

    Tuesday, June 15, 2010 7:21 PM
  • Thank you Scotty. Piling up is not the issue as I explained. I would rathre lose messages. What I need is that the threads should not pile up. If you are talking about threads it is another matter.

    I had to delete Join method. It jheld the execution for a few seconds.


    AlexB - Win_7 Pro64, SqlSer64 WinSer64
    Wednesday, June 16, 2010 7:15 PM
  • Yes, putting a limit on the queue size stops the messages from piling up and throws them away when the queue is full. Threads will not pile up by doing it the right way as Tergiver suggested, since you will only have one thread.
    Wednesday, June 16, 2010 8:23 PM
  • I edited the code above and expanded it so that the queue's behavior is customizable.

    There are two new (static) properties of TSP:

    1) MaxQueue
    2) DropNewest

    MaxQueue determains how many items will be queued up if they are coming in faster than they can be spoken. It defaults to 1 since it sounds like that's what you would want.

    DropNewest determains how to ignore incomming items when the queue is full. False (default) means that it will queue up this newer item and drop the oldest one. True means that it will drop (ignore) the new incomming item.

     

    Friday, June 18, 2010 1:16 AM
  • Than you Tergiver. I had a short leave of absence from my computer and will try your code in the next couple of days.


    AlexB - Win_7 Pro64, SqlSer64 WinSer64
    Sunday, June 20, 2010 9:32 PM