Introduction
This tip will help you understand and stress the importance of gracefully terminating running threads, spawned/ started by your ASP.NET application.
Background
Our project wanted that the workers/threads started by the Service (Integration Service- windows service, in our project, that ran several worker threads), exit gracefully when the Service Stop was issued or when it stopped due to some error.
Furthermore, in order to reduce memory leaks of the running application, specifically, when a "STOP
" to the service was issued, wanted to ensure that the running threads which are already started do not just be in an inconsistent state, but gracefully exit before termination.
Using the Code
public partial class OMSIntegration : ServiceBase
{
public OMSIntegration()
{
InitializeComponent();
}
private Thread syncThread;
private Thread workflowThread;
private SyncWorker syncWorker;
private WorkflowWorker workflowWorker;
private static Logger logger = LogManager.GetCurrentClassLogger();
protected override void OnStart(string[] args)
{
IsServiceRunning = true;
syncWorker = new SyncWorker();
syncThread = new Thread(syncWorker.Process);
if (! syncThread.IsAlive)
{
syncThread.Start(this);
logger.Info("OnStart: SyncWorker thread initialized.");
}
workflowWorker = new WorkflowWorker();
workflowThread = new Thread(workflowWorker.Process);
if (! workflowThread.IsAlive)
{
workflowThread.Start(this);
logger.Info("OnStart: WorkflowWorker thread initialized.");
}
logger.Info("OnStart: OMSIntegrationService is started.");
}
protected override void OnStop()
{
IsServiceRunning = false;
logger.Info("OnStop: OMSIntegrationService is stopping.");
JoinThreads();
base.OnStop();
}
private void JoinThreads()
{
int extraTime = System.Convert.ToInt32
(ConfigurationManager.AppSettings["ServiceAdditionalTimeInSecs"]) * 1000;
syncWorker.RequestStop();
syncThread.Join(extraTime);
Task.Factory.StartNew(() => IsThread_Running(syncThread,extraTime));
workflowWorker.RequestStop();
workflowThread.Join(extraTime);
Task.Factory.StartNew(() => IsThread_Running(workflowThread, extraTime));
}
private void IsThread_Running(Thread thread,int timeOut)
{
while(thread.ThreadState == System.Threading.ThreadState.Running &&
thread.ThreadState != System.Threading.ThreadState.WaitSleepJoin &&
thread.ThreadState != System.Threading.ThreadState.Stopped)
{
RequestAdditionalTime(timeOut);
}
}
}
public class SyncWorker : WorkerBase
{
private volatile bool _shouldStop;
private string serviceName = ConfigurationManager.AppSettings["ServiceInstanceName"].ToString();
public override void RequestStop()
{
_shouldStop = true;
}
public void Process(object service)
{
ServiceInstance = (OMSIntegration)service;
while (ServiceInstance.IsServiceRunning)
{
.....
.......
......
if (!_shouldStop)
{
logger.Debug("SyncWorker.Process:
Sleeping for the specified interval before resuming polling.");
Thread.Sleep(pollingWaitSeconds);
}
}
}
}
<appSettings>
<add key="ServicePauseIntervalInSeconds" value="60" />
<add key="ServiceAdditionalTimeInSecs" value="5" />
<add key="ServiceInstanceName" value="OMSIntegrationService" />
<add key="SyncWorkerWaitSeconds" value="1" />
<add key="SyncPollingFailureWaitMinutes" value="15" />
<add key="SyncRetryLimit" value="5" />
<add key="SyncRetryDelayMinutes" value="2" />
<add key="SyncThreadPoolSize" value="16" />
<add key="SyncItemsBatchSize" value="200" />
<add key="WorkflowWorkerWaitSeconds" value="1" />
<add key="WorkflowWorkerPollingFailureWaitMinutes" value="15" />
<add key="WorkflowWorkerRetryLimit" value="5" />
<add key="WorkflowRetryDelaySeconds" value="5" />
<add key="WorkflowWorkerThreadPoolSize" value="1" />
<add key="WorkflowWorkerItemsBatchSize" value="10" />
</appSettings>
Points of Interest
As soon as the Service "Stop
" is issued, calls JoinThreads
method, that finds out whether the relevant thread is running or not, if running requests additional time of 5 secs (configured in web config) for additional processing of already picked up items and then stops. By implementing this code, once you manually stop the Windows Service, it will take some time to finish the processing and then stop, as opposed to stopping immediately without this code.
One important note though, one cannot have an "Additional Time" of more than 2 minutes - I have read it somewhere - I will post the link if I happen to find it.