I’m building a Windows Service base class to manages the polling off a schedule’s table of any pending task and the running them.
The Windows service is using the System.Timers.Timer to start the schedule’s table polling.
I’m setting the ThreadPool.SetMaxThread to 10 before initialising the timer.
protected override void OnStart(string[] args)
{
ThreadPool.SetMaxThreads(10, 10);
this._Timer = new System.Timers.Timer();
this._Timer.Elapsed += new ElapsedEventHandler(PollWrapper);
this._Timer.Interval = 100;
this._Timer.Enabled = true;
}
The delegate method called by the timer keeps the count of the running threads so that it can be used in the OnStop() method to wait for each thread to complete before disposing the service.
private void PollWrapper(object sender, ElapsedEventArgs e)
{
numberOfRunningThreads++;
try
{
this.Poll(sender, e);
}
catch (Exception exception)
{
//some error logging here
}
finally
{
numberOfRunningThreads--;
}
}
protected override void OnStop()
{
this._Timer.Enabled = false;
while (numberOfRunningThreads > 0)
{
this.RequestAdditionalTime(1000);
Thread.Sleep(1000);
}
}
Often, the service would not stop when I try to stop it from the Windows service management console. If I debug it and add a breakpoint to the OnStop() method I can see that it is not because the numberOfRunningThreads is stuck on a number greater than 0 (often much greater than 10!). No tasks are running and it stays on that number forever!
Firstly, I don’t understand how that number could ever be greater than 10, despite the ThreadPool.SetMaxThreads should limit it to 10?
Secondly, even if I did not set the maximum number of threads, I would expect the finally block of the PollWrapper’s to eventually bring the count back to 0. If the counter stays greater than 0, it can be explained only with the finally block not executing, right!? How that is possible?
And lastly, would you suggest a differently way to limit the Poll to a number of possible concurrent running threads to a fixed number (.NET 3.5)?
Many thanks.
UPDATE:
After reading Yahia’s comments on reentrancy and SetMaxThread I have modified the PollWrapper so that it should always limit the max number of spawned running threads. I will still to make sure Poll is reentrant.
private void PollWrapper(object sender, ElapsedEventArgs e)
{
lock(this)
{
if(this.numberOfRunningThreads < this.numberOfAllowedThreads)
{
this.numberOfRunningThreads++;
Thread t = new Thread(
() =>
{
try
{
this.Poll(sender, e);
}
catch (Exception ex)
{
//log exception
}
finally
{
Interlocked.Decrement(ref this.numberOfRunningThreads);
}
}
);
t.Start();
}
}
As per request of OP (see comments above) in regards to the updated question/code:
The
lockstatement behaves like a “critical” section i.e. it stops that specific code block from being executed in parallel – the parameter oflockis used as the “syncrhonization object” so that any code block surrounded by alockon the same variable wouldn’t be executed in parallel on a different thread.The lock statement doesn’t do anything with the variable/its contents – for example it doesn’t “freeze” the contents so they can still be change anywhere anytime in any executing code (parallel) as long as that code is not protected by
lockand the same variable as a parameter.If you look at the link I provided (your points to an outdated documentation version for VS2003) it says explicitely that lock(this) for example is NOT ok to use – the reason and a best practice is described accordingly…
ALL of the above renders the provided code (updated version) still not thread-safe (although definitively better than the original version).