I am trying to build some kind of multi-thread game engine with reusable worker threads. That means the workers are running an endless loop and, after every iteration, call a method that suspends the thread with an AutoResetEvent until the next iteration is started from outside. Thet works fine.
But then I want to wait for all of the worker threads to get into the waiting state. But the last thread is always ignored in the first step.
This is my code:
class WorkerThread
{
private readonly Game game;
private readonly Task task;
private readonly AutoResetEvent waitEvent;
private readonly AutoResetEvent finishEvent;
public string Message { get; set; }
public bool Active { get; private set; }
public WorkerThread(Game game)
{
this.game = game;
waitEvent = new AutoResetEvent(false);
finishEvent = new AutoResetEvent(false);
task = new Task(startTask);
}
public void Start()
{
task.Start();
}
public void PerformFrame()
{
finishEvent.Reset();
waitEvent.Set();
}
public void WaitForFrameToFinish()
{
finishEvent.WaitOne();
}
private void startTask()
{
WaitFrame();
run();
}
long sum;
private void run()
{
while (true)
{
sum = 0;
for (int i = 0; i < 1000000; i++)
{
sum += i;
}
Console.WriteLine(Message);
WaitFrame();
}
}
private void WaitFrame()
{
Active = false;
finishEvent.Set();
waitEvent.WaitOne();
Active = true;
}
}
class Game
{
private readonly List<WorkerThread> workers = new List<WorkerThread>();
public Game()
{
workers.Add(new WorkerThread(this) { Message = "Worker 1" });
workers.Add(new WorkerThread(this) { Message = "Worker 2" });
workers.Add(new WorkerThread(this) { Message = "Worker 3" });
workers.Add(new WorkerThread(this) { Message = "Worker 4" });
workers.Add(new WorkerThread(this) { Message = "Worker 5" });
workers.Add(new WorkerThread(this) { Message = "Worker 6" });
}
public void Start()
{
foreach (WorkerThread worker in workers)
{
worker.Start();
}
}
public void OneFrame()
{
//start every worker
foreach (WorkerThread worker in workers)
{
worker.PerformFrame();
}
//wait for the workers to finish
foreach (WorkerThread worker in workers)
{
worker.WaitForFrameToFinish();
}
//check if there are workers still running
foreach (WorkerThread worker in workers)
{
if (worker.Active)
{
Console.WriteLine(worker.Message + " still active");
}
}
Console.WriteLine("Frame finished.\n");
}
}
Has anyone an idea why the Game doen't wait for the last WorkerThread do finish its task?
EDIT: Using a ManualResetEventSlim instead of an AutoResetEvent solves the problem. But I still don't understand why the above doesn't work.
EDIT 2: I found the answer and posted it below.
I just found the solution. startTask uses the WaitFrame method that sets the finishEvent. That way WaitForFrameToFinish will not wait for the worker on the first iteration because finishEvent is already set.
If you do it like this, it works fine:
private void startTask()
{
waitEvent.WaitOne();
waitEvent.Reset();
finishEvent.Reset();
run();
}
Related
I have a queue wrapper class which is storing items to a list, and multiple threads in the main program that are consuming these items until the list is empty. In this moment, the threads have to wait until the buffer will enqueue more items to the list.
I have an event fired every time that an item is enqueued, and I need to notify the threads that they have fresh items ready to consume.
public event EventHandler ItemEnqueued;
public void Enqueue(string item)
{
_itemsList.Add(item);
OnItemEnqueued();
}
void OnItemEnqueued()
{
ItemEnqueued?.Invoke(this, EventArgs.Empty);
}
How can I notify the threads of the main program that an item has been enqueued?
Many thanks!
EDIT to clarify
public class Queue
{
private readonly List<string> _itemsList = new List<string>();
public void Enqueue(String val)
{
_itemsList.Add(val);
OnItemEnqueued();
}
void OnItemEnqueued()
{
//here I have to tell the threads that a new item has been added
}
public string Dequeue()
{
//FIFO queue
if (_itemsList.Any())
{
var first = _itemsList.First();
_itemsList.RemoveAt(0);
return first;
}
return default(string);
}
public int Count()
{
return _itemsList.Count;
}
}
}
And I have to create two threads and make them "fight" for the enqueued items:
class Program
{
readonly object _syncLock = new object();
Queue _q = new Queue();
static void Main(string[] args)
{
Program p = new Program();
p.InitThreads();
}
public void InitThreads()
{
lock (_syncLock) //exclusively add items
{
for (var i = 0; i <= 99; i++)
{
_q.Enqueue(i.ToString());
}
}
Thread t1 = new Thread(() => {
while(_q.Count() > 0)
Console.WriteLine("T1 dequeued " + Consume());
});
Thread t2 = new Thread(() =>
{
while (_q.Count() > 0)
Console.WriteLine("T2 dequeued " + Consume());
});
t1.Start();
t2.Start();
//at some moment I have to tell the threads to wait until a new item has been enqueued
}
public string Consume()
{
lock (_syncLock) //safely get one item
{
return _q.Dequeue();
}
}
}
Not sure if this is what you're looking for, but here's a compliable code sample that demonstrates how to use AutoResetEvent to signal just one thread at once:
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApp1
{
static class Program
{
public static void Main()
{
var signal = new AutoResetEvent(initialState:false);
for (int i = 0; i < 4; ++i)
Task.Run(() => worker(signal));
while (true)
{
Console.WriteLine("Press <ENTER> to wake a thread.");
Console.ReadLine();
signal.Set();
}
}
static void worker(AutoResetEvent signal)
{
int threadId = Thread.CurrentThread.ManagedThreadId;
while (true)
{
Console.WriteLine($"Thread {threadId} is waiting for a signal.");
signal.WaitOne();
Console.WriteLine($"Thread {threadId} received a signal");
}
}
}
}
The key thing to note is that you create a single AutoResetEvent and pass it to all the threads, which then wait on it. Then the "main" thread signals the event when it wants one of the threads to respond.
I wouldn't use a ManualResetEvent for this kind of thing, because then all the threads would be signalled simultaneously, which it looks like you want to avoid.
I'm messing around with multithreading and making some sort of task engine. The idea is that the engine can have a configurable amount of threads waiting and when a new task arrives the first free thread picks it up and executes it.
The problem is that something 2 threads pickup the same task somehow. I looked it through and I think that this code should work but obviously it doesn't. If I add the 10ms sleep where it is now commented out it works, but I'm not sure I understand why. It looks like the .Reset() function returns before it actually resets the event?
Can somebody explain? Is there a better way to let only a single thread continue when there are multiple waiting?
Thanks
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace TaskTest
{
public class Engine
{
private ManualResetEvent taskEvent;
private ConcurrentQueue<Task> tasks;
private bool running;
private List<Thread> threads;
private int threadAmount;
private int threadsBusy = 0;
public Engine(int amountOfThreads)
{
taskEvent = new ManualResetEvent(false);
tasks = new ConcurrentQueue<Task>();
threads = new List<Thread>();
threadAmount = amountOfThreads;
}
public void Start()
{
running = true;
for (var i = 0; i < threadAmount; i++)
{
var thread = new Thread(Process);
thread.Name = "Thread " + i;
threads.Add(thread);
thread.Start();
}
}
public void Stop()
{
running = false;
taskEvent.Set();
threads.ForEach(t => t.Join());
}
private void Process()
{
while (running)
{
lock (taskEvent)
{
// Lock it so only a single thread is waiting on the event at the same time
taskEvent.WaitOne();
taskEvent.Reset();
//Thread.Sleep(10);
}
if (!running)
{
taskEvent.Set();
return;
}
threadsBusy += 1;
if (threadsBusy > 1)
Console.WriteLine("Failed");
Task task;
if (tasks.TryDequeue(out task))
task.Execute();
threadsBusy -= 1;
}
}
public void Enqueue(Task t)
{
tasks.Enqueue(t);
taskEvent.Set();
}
}
}
EDIT
Rest of the code:
namespace TaskTest
{
public class Start
{
public static void Main(params string[] args)
{
var engine = new Engine(4);
engine.Start();
while (true)
{
Console.Read();
engine.Enqueue(new Task());
}
}
}
}
namespace TaskTest
{
public class Task
{
public void Execute()
{
Console.WriteLine(Thread.CurrentThread.Name);
}
}
}
When using Console.Read() on a key press, two characters are read from the input. You should use Console.ReadLine() instead.
Note that your code can be simplified a lot by using a BlockingCollection to handle the synchronization:
public class Engine
{
private BlockingCollection<Task> tasks;
private List<Thread> threads;
private int threadAmount;
public Engine(int amountOfThreads)
{
tasks = new BlockingCollection<Task>();
threads = new List<Thread>();
threadAmount = amountOfThreads;
}
public void Start()
{
for (var i = 0; i < threadAmount; i++)
{
var thread = new Thread(Process);
thread.Name = "Thread " + i;
threads.Add(thread);
thread.Start();
}
}
public void Stop()
{
tasks.CompleteAdding();
threads.ForEach(t => t.Join());
}
private void Process()
{
foreach (var task in tasks.GetConsumingEnumerable())
{
task.Execute();
}
}
public void Enqueue(Task t)
{
tasks.Add(t);
}
}
When using the StartNew() method to kick off a process on a new thread, I need to figure out how to make another call into this object in that same thread (I assume this would be some sort of Join operation?).
The following example is dumbed down to illustrate the meat of what I am trying to do. I am well aware it is severely lacking in basic concurrency considerations. But I didn't want to cloud the code with all of that logic, so please forgive me on that.
The following console app shows what I am trying to accomplish. Assume on the StartNew() call a new thread with ID 9976 is created and the method invoked there. I would like the subsequent call to ProcessImmediate() in the file system watcher change event handler to be made on thread 9976 as well. As it stands, the call would share the same thread that is used for the file system watcher change event.
Can this be done, and if so, how?
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
var runner = new Runner();
runner.Run();
Console.ReadKey();
}
}
public class Runner
{
private Activity _activity = null;
private FileSystemWatcher _fileSystemWatcher;
public void Run()
{
_activity = new Activity();
// start activity on a new thread
Task.Factory.StartNew(() => _activity.Go());
_fileSystemWatcher = new FileSystemWatcher();
_fileSystemWatcher.Filter = "*.watcher";
_fileSystemWatcher.Path = "c:\temp";
_fileSystemWatcher.Changed += FileSystemWatcher_Changed;
_fileSystemWatcher.EnableRaisingEvents = true;
}
private void FileSystemWatcher_Changed(object sender, FileSystemEventArgs e)
{
// WANT TO CALL THIS FOR ACTIVITY RUNNING ON PREVIOUSLY CALLED THREAD
_activity.ProcessImmediate();
}
}
public class Activity
{
public void Go()
{
while (!Stop)
{
// for purposes of this example, magically assume that ProcessImmediate has not been called when this is called
DoSomethingInteresting();
System.Threading.Thread.Sleep(2000);
}
}
protected virtual void DoSomethingInteresting() { }
public void ProcessImmediate()
{
// for purposes of this example, assume that Go is magically in its sleep state when ProcessImmediate is called
DoSomethingInteresting();
}
public bool Stop { get; set; }
}
}
* UPDATE *
Thanks for the excellent responses. I took Mike's suggestion and implemented it for my console app. Below is the full working code which also includes the use of a cancellation token. I post this in case someone else might find it useful.
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
var runner = new Runner();
runner.Run();
Console.ReadKey();
runner.Stop();
Console.ReadKey();
}
}
public class Runner
{
private Activity _activity = null;
private FileSystemWatcher _fileSystemWatcher;
private CancellationTokenSource _cts = new CancellationTokenSource();
public void Stop() { _cts.Cancel(); }
public void Run()
{
_activity = new Activity();
// start activity on a new thread
var task = new Task(() => _activity.Go(_cts.Token), _cts.Token, TaskCreationOptions.LongRunning);
task.Start();
_fileSystemWatcher = new FileSystemWatcher();
_fileSystemWatcher.Filter = "*.watcher";
_fileSystemWatcher.Path = "C:\\Temp\\FileSystemWatcherPath";
_fileSystemWatcher.Changed += FileSystemWatcher_Changed;
_fileSystemWatcher.EnableRaisingEvents = true;
}
private void FileSystemWatcher_Changed(object sender, FileSystemEventArgs e)
{
// WANT TO CALL THIS FOR ACTIVITY RUNNING ON PREVIOUSLY CALLED THREAD
_activity.ProcessImmediate();
}
}
public class Activity : IDisposable
{
private AutoResetEvent _processing = new AutoResetEvent(false);
public void Go(CancellationToken ct)
{
Thread.CurrentThread.Name = "Go";
while (!ct.IsCancellationRequested)
{
// for purposes of this example, magically assume that ProcessImmediate has not been called when this is called
DoSomethingInteresting();
_processing.WaitOne(5000);
}
Console.WriteLine("Exiting");
}
protected virtual void DoSomethingInteresting()
{
Console.WriteLine(string.Format("Doing Something Interesting on thread {0}", Thread.CurrentThread.ManagedThreadId));
}
public void ProcessImmediate()
{
// for purposes of this example, assume that Go is magically in its sleep state when ProcessImmediate is called
_processing.Set();
}
public void Dispose()
{
if (_processing != null)
{
_processing.Dispose();
_processing = null;
}
}
}
}
First, you should use TaskCreationOptions.LongRunning if you are creating a task that will not complete quickly. Second, use an AutoResetEvent to signal the waiting thread to wake up. Note that below ProcessImmediate will return before DoSomethingInteresting has completed running on the other thread. Example:
using System.Threading;
public class Activity : IDisposable
{
private AutoResetEvent _processing = new AutoResetEvent(false);
public void Go()
{
while (!Stop)
{
// for purposes of this example, magically assume that ProcessImmediate has not been called when this is called
DoSomethingInteresting();
_processing.WaitOne(2000);
}
}
protected virtual void DoSomethingInteresting() { }
public void ProcessImmediate()
{
_processing.Set();
}
public bool Stop { get; set; }
public void Dispose()
{
if (_processing != null)
{
_processing.Dispose();
_processing = null;
}
}
}
User mike has given a better solution, which will be appropriate when you like to call the same method immediately. If you want to call a different methods immediately I'll expand mike's answer to achieve that.
using System.Threading;
public class Activity : IDisposable
{
private AutoResetEvent _processing = new AutoResetEvent(false);
private ConcurrentQueue<Action> actionsToProcess = new ConcurrentQueue<Action>();
public void Go()
{
while (!Stop)
{
// for purposes of this example, magically assume that ProcessImmediate has not been called when this is called
DoSomethingInteresting();
_processing.WaitOne(2000);
while(!actionsToProcess.IsEmpty)
{
Action action;
if(actionsToProcess.TryDeque(out action))
action();
}
}
}
protected virtual void DoSomethingInteresting() { }
public void ProcessImmediate(Action action)
{
actionsToProcess.Enqueue(action);
_processing.Set();
}
public bool Stop { get; set; }
public void Dispose()
{
if (_processing != null)
{
_processing.Dispose();
_processing = null;
}
}
}
To execute different methods on the same thread you can use a message loop that dispatches incoming requests. A simple option would be to use the event loop scheduler of the Reactive Extensions and to "recursively" schedule your Go() function - if in the mean time a different operation is scheduled it would be processed before the next Go() operation.
Here is a sample:
class Loop
: IDisposable
{
IScheduler scheduler = new EventLoopScheduler();
MultipleAssignmentDisposable stopper = new MultipleAssignmentDisposable();
public Loop()
{
Next();
}
void Next()
{
if (!stopper.IsDisposed)
stopper.Disposable = scheduler.Schedule(Handler);
}
void Handler()
{
Thread.Sleep(1000);
Console.WriteLine("Handler: {0}", Thread.CurrentThread.ManagedThreadId);
Next();
}
public void Notify()
{
scheduler.Schedule(() =>
{
Console.WriteLine("Notify: {0}", Thread.CurrentThread.ManagedThreadId);
});
}
public void Dispose()
{
stopper.Dispose();
}
}
static void Main(string[] args)
{
using (var l = new Loop())
{
Console.WriteLine("Press 'q' to quit.");
while (Console.ReadKey().Key != ConsoleKey.Q)
l.Notify();
}
}
I'm having trouble understanding why my console app doesn't wait until the thread it spawns fully terminates. I think this is related to the fact that the thread in question also spawns its own child threads and/or the inclusion of System.Timer
The basic program flow is as follows. Main creates a new thread against the Simulator.Start method, and then joins until that thread terminates. Simulator.Start creates a new Timer (to constrain how long it should execute) and then creates/runs a bunch of child threads. When the Elapsed event is raised by the Timer, this signals that the Simulator should terminate all of its child threads and generate a report. The problem is that the console app exits as soon as all child threads terminate and the code to generate a report never gets executed (See Simulator.Stop method below).
Hopefully some pseudo code will help:
public class Program
{
private static Simulator _simulator;
private static void Main(string[] args)
{
var options = new SimulationOptions();
//check for valid options
if (!Parser.Default.ParseArguments(args, options)) return;
_simulator = new Simulator(options);
var thread = new Thread(_simulator.Start) {IsBackground = false};
thread.Start();
thread.Join();
}
}
public class Simulator
{
private readonly SimulationOptions _options;
private readonly List<Thread> _threads = new List<Thread>();
private readonly List<Worker> _workers = new List<Worker>();
private static Timer _timer;
public Simulator(SimulationOptions options)
{
_options = options;
StartTimer(_options.LengthOfTest);
}
private void StartTimer(int lengthOfTest)
{
_timer = new Timer {Interval = lengthOfTest*1000};
_timer.Elapsed += Timer_Elapsed;
_timer.Start();
}
private void Timer_Elapsed(object sender, ElapsedEventArgs e)
{
_timer.Stop();
Stop();
}
public void Stop()
{
// Request that the worker thread stop itself:
foreach (Worker worker in _workers)
{
worker.RequestStop();
}
GenerateReport(); //<-- this code never gets executed
}
private XDocument GenerateReport()
{
//build an awesome report
}
public void Start()
{
_threads.Clear();
_workers.Clear();
for (int i = 0; i < _options.NumberOfClients; i++)
{
_workers.Add(new Worker());
_threads.Add(new Thread(_workers.Last().PumpMessages));
_threads.Last().Start();
}
}
}
public class Worker
{
private bool _shouldStop = false;
public void PumpMessages()
{
while (!_shouldStop)
{
//does cool stuff until told to stop
}
}
public void RequestStop()
{
_shouldStop = true;
}
}
Nothing in your start method keeps the thread alive. When the following method finishes, so does the thread. You then call Thread.Join and that is the end of that.
public void Start()
{
_threads.Clear();
_workers.Clear();
for (int i = 0; i < _options.NumberOfClients; i++)
{
_workers.Add(new Worker());
_threads.Add(new Thread(_workers.Last().PumpMessages));
_threads.Last().Start();
}
}
If you intend on waiting for this work to complete, consider waiting on a ManualResetEvent for each worker thread that you are using.
http://msdn.microsoft.com/en-us/library/system.threading.manualresetevent.aspx
http://msdn.microsoft.com/en-us/library/system.threading.waithandle.waitall.aspx
Your method should look something like the following.
public void Start()
{
_threads.Clear();
_workers.Clear();
var evts = new List<ManualResetEvent>()
for (int i = 0; i < _options.NumberOfClients; i++)
{
ManualResetEvent evt = new ManualResetEvent(false);
evts.Add(evt);
_workers.Add(new Worker(evt));
_threads.Add(new Thread(_workers.Last().PumpMessages));
_threads.Last().Start();
}
WaitHandle.WaitAll(evts.ToArray());
}
public class Worker
{
private bool _shouldStop = false;
private readonly ManualResetEvent #event;
public Worker(ManualResetEvent #event)
{
this.#event = #event;
}
public void PumpMessages()
{
while (!_shouldStop)
{
//does cool stuff until told to stop
}
#event.Set();
}
public void RequestStop()
{
_shouldStop = true;
}
}
Join method waits only for thread instance you joined, so Simulator.Start just creates some threads and it terminates, as the result Join returns and your main thread terminates. But still your App is alive(reason some other Foreground threads are still running).
generate a report never gets executed? Why?
Process will terminate when all Foreground Threads terminates. so as soon as your child threads return from PumpMessages method when you call RequestStop in a loop, all of your foreground threads terminates
public void Stop()
{
// Request that the worker thread stop itself:
foreach (Worker worker in _workers)
{
worker.RequestStop();
}
<--here all foreground threads are ready to terminate
GenerateReport(); //<-- this code never gets executed
}
It was little misleading that I stated that all foreground threads die after the loop. To make it clear let's say that we have given instruction for the worker threads to stop working, so all threads may or may not die before executing GenerateReport method. yes there is a Race If worker threads wins the race then we lose it, and viceversa. sometimes your GenerateReport may execute without any problem.
How to fix it? We just wait for all our worker threads to terminate. that's it.
public void Start()
{
_threads.Clear();
_workers.Clear();
for (int i = 0; i < _options.NumberOfClients; i++)
{
_workers.Add(new Worker());
_threads.Add(new Thread(_workers.Last().PumpMessages));
_threads.Last().Start();
}
foreach (var t in _threads)
t.Join();
}
The scenario is as follows:
There are a couple of low priority threads that can be interrupted by high priority threads. Whenever a high priority thread asks the low priority threads to pause, they will go to Wait state (if they are not in wait state already). However when a high priority thread signals that the low priority threads can Resume, the low priority threads should not resume until all the high priority threads that asked the low priority threads to pause have consented.
To solve this I am keeping a track of Pause() calls from the high priority threads to the low priority thread in a counter variable. Whenever the high priority thread asks the low priority thread to Pause(), the value of the counter is incremented by 1. If after the increment the counter has a value of 1, it means the thread was not in Wait, so ask it to go in Wait state. Otherwise just increment counter value. On the contrary when a high priority thread calls Resume() we decrement the counter value and if after the decrement the value is 0, it means the low priority threads can Resume now.
Here is a simplified implementation of my problem. The comparison operation inside if statements with Interlocked.XXX is not correct i.e.
if (Interlocked.Increment(ref _remain) == 1)
, as the read/modify and comparison operations are not atomic.
What am I missing here? I don't want to use thread priority.
using System;
using System.Collections.Generic;
using System.Threading;
namespace TestConcurrency
{
// I borrowed this class from Joe Duffy's blog and modified it
public class LatchCounter
{
private long _remain;
private EventWaitHandle m_event;
private readonly object _lockObject;
public LatchCounter()
{
_remain = 0;
m_event = new ManualResetEvent(true);
_lockObject = new object();
}
public void Check()
{
if (Interlocked.Read(ref _remain) > 0)
{
m_event.WaitOne();
}
}
public void Increment()
{
lock(_lockObject)
{
if (Interlocked.Increment(ref _remain) == 1)
m_event.Reset();
}
}
public void Decrement()
{
lock(_lockObject)
{
// The last thread to signal also sets the event.
if (Interlocked.Decrement(ref _remain) == 0)
m_event.Set();
}
}
}
public class LowPriorityThreads
{
private List<Thread> _threads;
private LatchCounter _latch;
private int _threadCount = 1;
internal LowPriorityThreads(int threadCount)
{
_threadCount = threadCount;
_threads = new List<Thread>();
for (int i = 0; i < _threadCount; i++)
{
_threads.Add(new Thread(ThreadProc));
}
_latch = new CountdownLatch();
}
public void Start()
{
foreach (Thread t in _threads)
{
t.Start();
}
}
void ThreadProc()
{
while (true)
{
//Do something
Thread.Sleep(Rand.Next());
_latch.Check();
}
}
internal void Pause()
{
_latch.Increment();
}
internal void Resume()
{
_latch.Decrement();
}
}
public class HighPriorityThreads
{
private Thread _thread;
private LowPriorityThreads _lowPriorityThreads;
internal HighPriorityThreads(LowPriorityThreads lowPriorityThreads)
{
_lowPriorityThreads = lowPriorityThreads;
_thread = new Thread(RandomlyInterruptLowPriortyThreads);
}
public void Start()
{
_thread.Start();
}
void RandomlyInterruptLowPriortyThreads()
{
while (true)
{
Thread.Sleep(Rand.Next());
_lowPriorityThreads.Pause();
Thread.Sleep(Rand.Next());
_lowPriorityThreads.Resume();
}
}
}
class Program
{
static void Main(string[] args)
{
LowPriorityThreads lowPriorityThreads = new LowPriorityThreads(3);
HighPriorityThreads highPriorityThreadOne = new HighPriorityThreads(lowPriorityThreads);
HighPriorityThreads highPriorityThreadTwo = new HighPriorityThreads(lowPriorityThreads);
lowPriorityThreads.Start();
highPriorityThreadOne.Start();
highPriorityThreadTwo.Start();
}
}
class Rand
{
internal static int Next()
{
// Guid idea has been borrowed from somewhere on StackOverFlow coz I like it
return new System.Random(Guid.NewGuid().GetHashCode()).Next() % 30000;
}
}
I don't know about your requirements hence I won't discuss them here.
As far as the implementation goes, I would introduce a "dispatcher" class that will handle inter-threads interaction and also acts a a factory for "runnable" objects.
The implementation, of course is very rough and open for criticism.
class Program
{
static void Main(string[] args)
{
ThreadDispatcher td=new ThreadDispatcher();
Runner r1 = td.CreateHpThread(d=>OnHpThreadRun(d,1));
Runner r2 = td.CreateHpThread(d => OnHpThreadRun(d, 2));
Runner l1 = td.CreateLpThread(d => Console.WriteLine("Running low priority thread 1"));
Runner l2 = td.CreateLpThread(d => Console.WriteLine("Running low priority thread 2"));
Runner l3 = td.CreateLpThread(d => Console.WriteLine("Running low priority thread 3"));
l1.Start();
l2.Start();
l3.Start();
r1.Start();
r2.Start();
Console.ReadLine();
l1.Stop();
l2.Stop();
l3.Stop();
r1.Stop();
r2.Stop();
}
private static void OnHpThreadRun(ThreadDispatcher d,int number)
{
Random r=new Random();
Thread.Sleep(r.Next(100,2000));
d.CheckedIn();
Console.WriteLine(string.Format("*** Starting High Priority Thread {0} ***",number));
Thread.Sleep(r.Next(100, 2000));
Console.WriteLine(string.Format("+++ Finishing High Priority Thread {0} +++", number));
Thread.Sleep(300);
d.CheckedOut();
}
}
public abstract class Runner
{
private Thread _thread;
protected readonly Action<ThreadDispatcher> _action;
private readonly ThreadDispatcher _dispathcer;
private long _running;
readonly ManualResetEvent _stopEvent=new ManualResetEvent(false);
protected Runner(Action<ThreadDispatcher> action,ThreadDispatcher dispathcer)
{
_action = action;
_dispathcer = dispathcer;
}
public void Start()
{
_thread = new Thread(OnThreadStart);
_running = 1;
_thread.Start();
}
public void Stop()
{
_stopEvent.Reset();
Interlocked.Exchange(ref _running, 0);
_stopEvent.WaitOne(2000);
_thread = null;
Console.WriteLine("The thread has been stopped.");
}
protected virtual void OnThreadStart()
{
while (Interlocked.Read(ref _running)!=0)
{
OnStartWork();
_action.Invoke(_dispathcer);
OnFinishWork();
}
OnFinishWork();
_stopEvent.Set();
}
protected abstract void OnStartWork();
protected abstract void OnFinishWork();
}
public class ThreadDispatcher
{
private readonly ManualResetEvent _signal=new ManualResetEvent(true);
private int _hpCheckedInThreads;
private readonly object _lockObject = new object();
public void CheckedIn()
{
lock(_lockObject)
{
_hpCheckedInThreads++;
_signal.Reset();
}
}
public void CheckedOut()
{
lock(_lockObject)
{
if(_hpCheckedInThreads>0)
_hpCheckedInThreads--;
if (_hpCheckedInThreads == 0)
_signal.Set();
}
}
private class HighPriorityThread:Runner
{
public HighPriorityThread(Action<ThreadDispatcher> action, ThreadDispatcher dispatcher) : base(action,dispatcher)
{
}
protected override void OnStartWork()
{
}
protected override void OnFinishWork()
{
}
}
private class LowPriorityRunner:Runner
{
private readonly ThreadDispatcher _dispatcher;
public LowPriorityRunner(Action<ThreadDispatcher> action, ThreadDispatcher dispatcher)
: base(action, dispatcher)
{
_dispatcher = dispatcher;
}
protected override void OnStartWork()
{
Console.WriteLine("LP Thread is waiting for a signal.");
_dispatcher._signal.WaitOne();
Console.WriteLine("LP Thread got the signal.");
}
protected override void OnFinishWork()
{
}
}
public Runner CreateLpThread(Action<ThreadDispatcher> action)
{
return new LowPriorityRunner(action, this);
}
public Runner CreateHpThread(Action<ThreadDispatcher> action)
{
return new HighPriorityThread(action, this);
}
}
}