Question

I'm trying to simulate (very basic & simple) OS process manager subsystem, I have three "processes" (workers) writing something to console (this is an example):

    public class Message
    {
        public Message() { }
        public void Show()
        {
            while (true)
            {
                Console.WriteLine("Something");
                Thread.Sleep(100);
            }
        }
    }

Each worker is supposed to be run on a different thread. That's how I do it now: I have a Process class which constructor takes Action delegate and starts a thread from it and suspends it.

public class Process
{
    Thread thrd;
    Action act;

    public Process(Action act)
    {
        this.act = act;

        thrd = new Thread(new ThreadStart(this.act));
        thrd.Start();
        thrd.Suspend();
    }

    public void Suspend()
    {
        thrd.Suspend();
    }

    public void Resume()
    {
        thrd.Resume();
    }
}

In that state it waits before my scheduler resumes it, gives it a time slice to run, then suspends it again.

        public void Scheduler()
        {
            while (true)
            {
                //ProcessQueue is just FIFO queue for processes
                //MainQueue is FIFO queue for ProcessQueue's
                ProcessQueue currentQueue = mainQueue.Dequeue();
                int count = currentQueue.Count;

                if (currentQueue.Count > 0)
                {
                    while (count > 0)
                    {
                        Process currentProcess = currentQueue.GetNext();

                        currentProcess.Resume();
                        //this is the time slice given to the process
                        Thread.Sleep(1000);
                        currentProcess.Suspend();

                        Console.WriteLine();
                        currentQueue.Add(currentProcess);

                        count--;
                    }
                }

                mainQueue.Enqueue(currentQueue);
            }
        }

The problem is that it doesn't work consistently. It even doesn't work at all in this state, i have to add Thread.Sleep() before WriteLine in Show() method of the worker, like this.

        public void Show()
        {
            while (true)
            {
                Thread.Sleep(100); //Without this line code doesn't work
                Console.WriteLine("Something");
                Thread.Sleep(100);
            }
        }

I've been trying to use ManualResetEvent instead of suspend/resume, it works, but since that event is shared, all threads relying on it wake up simultaneously, while I need only one specific thread to be active at one time.

If some could help me figure out how to pause/resume task/thread normally, that'd be great. What I'm doing is trying to simulate simple preemptive multitasking. Thanks.

Was it helpful?

Solution

Thread.Suspend is evil. It is about as evil as Thread.Abort. Almost no code is safe in the presence of being paused at arbitrary, unpredictable locations. It might hold a lock that causes other threads to pause as well. You quickly run into deadlocks or unpredictable stalls in other parts of the system.

Imagine you were accidentally pausing the static constructor of string. Now all code that wants to use a string is halted as well. Regex internally uses a locked cache. If you pause while this lock is taken all Regex related code might pause. These are just two egregious examples.

Probably, suspending some code deep inside the Console class is having unintended consequences.

I'm not sure what to recommend to you. This seems to be an academic exercise so thankfully this is not a production problem for you. User-mode waiting and cancellation must be cooperative in practice.

OTHER TIPS

I manage to solve this problem using static class with array of ManualResetEvent's, where each process is identified by it's unique ID. But I think it's pretty dirty way to do it. I'm open to other ways of accomplishing this. UPD: added locks to guarantee thread safety

public sealed class ControlEvent
    {
        private static ManualResetEvent[] control = new ManualResetEvent[100];

        private static readonly object _locker = new object();

        private ControlEvent() { }

        public static object Locker
        {
            get
            {
                return _locker;
            }
        }

        public static void Set(int PID)
        {
            control[PID].Set();
        }

        public static void Reset(int PID)
        {
            control[PID].Reset();
        }

        public static ManualResetEvent Init(int PID)
        {
            control[PID] = new ManualResetEvent(false);
            return control[PID];
        }
    }

In worker class

public class RandomNumber
    {
        static Random R = new Random();

        ManualResetEvent evt;

        public ManualResetEvent Event
        {
            get
            {
                return evt;
            }
            set
            {
                evt = value;
            }
        }

        public void Show()
        {
            while (true)
            {
                evt.WaitOne();
                lock (ControlEvent.Locker)
                {
                   Console.WriteLine("Random number: " + R.Next(1000));
                }
                Thread.Sleep(100);
            }
        }
    }

At Process creation event

RandomNumber R = new RandomNumber();

Process proc = new Process(new Action(R.Show));

R.Event = ControlEvent.Init(proc.PID);

And, finally, in scheduler

public void Scheduler()
        {
            while (true)
            {
                ProcessQueue currentQueue = mainQueue.Dequeue();
                int count = currentQueue.Count;

                if (currentQueue.Count > 0)
                {
                    while (count > 0)
                    {
                        Process currentProcess = currentQueue.GetNext();

                        //this wakes the thread
                        ControlEvent.Set(currentProcess.PID);

                        Thread.Sleep(quant);

                        //this makes it wait again
                        ControlEvent.Reset(currentProcess.PID);

                        currentQueue.Add(currentProcess);

                        count--;
                    }
                }

                mainQueue.Enqueue(currentQueue);
            }
        }

The single best advice I can give with regard to Suspend() and Resume(): Don't use it. You are doing it wrong™.

Whenever you feel a temptation to use Suspend() and Resume() pairs to control your threads, you should step back immediately and ask yourself, what you are doing here. I understand, that programmers tend to think of the execution of code paths as of something that must be controlled, like some dumb zombie worker that needs permament command and control. That's probably a function of the stuff learned about computers in school and university: Computers do only what you tell them.

Ladies & Gentlemen, here's the bad news: If you are doing it that way, this is called "micro management", and some even would call it "control freak thinking".

Instead, I would strongly encorage you to think about it in a different way. Try to think of your threads as intelligent entities, that do no harm and the only thing they want is to be fed with enough work. They just need a little guidance, that's all. You may place a container full of work just in front of them (work task queue) and have them pulling the tasks from that container themselves, as soon as the finished their previous task. When the container is empty, all tasks are processed and there's nothing left to do, they are allowed to fall asleep and WaitFor(alarm) which will be signaled whenever new tasks arrive.

So instead of command-and-controlling a herd of dumb zombie slaves that can't do anything right without you cracking the whip behind them, you deliberately guide a team of intelligent co-workers and just let it happen. That's the way a scalable architecture is built. You don't have to be a control freak, just have a little faith in your own code.

Of course, as always, there are exceptions to that rule. But there aren't that many, and I would recommend to start with the work hypothesis, that your code is probably the rule, rather than the exception.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top