C# inheritance

R

rick

I have a service that runs jobs. Each job is an instance of a class
that inherits a CJob class. The service has a collection of objects
(job classes). Each job has an inherited property named Running from
the CJob class. The service checks the status of each jobs Running
property every second. Each job class has a Run method.

Is it possible that the service gets confused and misreads the Running
property? In other words, the code looks like it is looking at the
Running property of Job1 but in fact it is looking at Job2. My partner
believes this can happen due to multiple CPUs. Is he crazy? Or am I
stupid? It is probably the later.

namespace KPI
{

public abstract class CJob
{
public override void Run() {}
private bool mbRunning;
public bool Running {
get { return mbRunning; }
set { mbRunning = value; }
}
}

public class CExportSales : CJob
{
public CExportSales() {}

public override void Run() { // job code written here }
}
}

// SERVICE
namespace KPIService
{
class KPIServiceMain
{
private void RunJobs()
{
int iThreads = 0;
foreach (CJob oJob in moJobs.Values)
{
if(oJob.Enabled == 1) {
if (oJob.Running) {
iThreads++;
} else {
Thread oThread = new Thread(new
ThreadStart(oJob.Run));
oThread.Start();
}
if (iThreads >= 10) { break; }
}
}
}
}
}

/// The job objects are loaded from database records

oJob =
(CJob)Activator.CreateInstance(Type.GetType(dr["class"].ToString() +
", KPI"));
oJob.Name = dr["name"].ToString();
oJob.Enabled = Convert.ToInt32(dr["enabled"]);
moJobs.Add(oJob.Name, oJob);
 
J

Jon Skeet [C# MVP]

rick said:
I have a service that runs jobs. Each job is an instance of a class
that inherits a CJob class. The service has a collection of objects
(job classes). Each job has an inherited property named Running from
the CJob class. The service checks the status of each jobs Running
property every second. Each job class has a Run method.

Is it possible that the service gets confused and misreads the Running
property? In other words, the code looks like it is looking at the
Running property of Job1 but in fact it is looking at Job2. My partner
believes this can happen due to multiple CPUs. Is he crazy? Or am I
stupid? It is probably the later.

You've provided some code, but nothing we can really run.

Could you post a short but complete program which demonstrates the
problem?

See http://www.pobox.com/~skeet/csharp/complete.html for details of
what I mean by that.
 
I

Ignacio Machin \( .NET/ C# MVP \)

Hi,


What is the error you are seeing?

Also from the code you are just creating 10 threads and then you break the
loop, how do yo rn the other jobs?
How do you load the jobs?

I do not understand that of the "confusion" in the code. Could you explain
it further?
 
P

Peter Duniho

I have a service that runs jobs. Each job is an instance of a class
that inherits a CJob class. The service has a collection of objects
(job classes). Each job has an inherited property named Running from
the CJob class. The service checks the status of each jobs Running
property every second. Each job class has a Run method.

Is it possible that the service gets confused and misreads the Running
property? In other words, the code looks like it is looking at the
Running property of Job1 but in fact it is looking at Job2. My partner
believes this can happen due to multiple CPUs. Is he crazy? Or am I
stupid? It is probably the later.

As Jon and Ignacio point out, you haven't really provided a complete
question or problem description.

That said, just to clarify the basics: no, it is not possible for
inheritance to cause trouble with respect to what value a given thread
reads from a given instance of an object.

It certainly is possible for _other_ threading issues to exist, and those
issues do often manifest themselves more frequently when you have multiple
CPUs. But in no case would that have something to do with object
inheritance, as your subject line suggests you are asking about.

Pete
 
C

Cor Ligthert[MVP]

Rick,

Have a look at your timer, you will not be the first where the second
process start at almost the same time as the first, just because the Timer
ticking mechanism is not disabled and enabled and goes on and on and on and
then even is passing each other.

Cor
 
M

Michael C

rick said:
I have a service that runs jobs. Each job is an instance of a class
that inherits a CJob class. The service has a collection of objects
(job classes). Each job has an inherited property named Running from
the CJob class. The service checks the status of each jobs Running
property every second. Each job class has a Run method.

This sound remarkably similar to my service except I called the base class
Task instead of CJob. I even have the Run method and Running property.
Is it possible that the service gets confused and misreads the Running
property? In other words, the code looks like it is looking at the
Running property of Job1 but in fact it is looking at Job2. My partner
believes this can happen due to multiple CPUs. Is he crazy? Or am I
stupid? It is probably the later.

I suspect he is not crazy but is wrong. Somehow you or him is
misunderstanding what is going wrong. I would guess you have a reference to
Job2 when you expect Job1.

Michael
 
R

rick

We are not seeing an error. My partner suspects believes in theory
there could be a problem. He believes that in the RunJobs method, I
could actually be reading the Running property on Job2 when I think I
am reading Running on Job1.

I basically have a number of classes that I use for the jobs. One
class equals one job. None of the job classes call another job class.
In other words Job1 would never call in code in Job2.

Here is how I load the jobs. How they run is below LoadSchedule.

public void LoadSchedule() {
CJob oJob = null;
string sSQL;
string sName = "";
string sStatus;
string sLoad = "";
try {

SqlConnection Cn = moDb.ConnectSqlDB(CBase.gsCCN_DB);

sSQL = "SELECT terminate FROM tBackend ";
sSQL = sSQL + " WHERE logname = 'KPI'";
sSQL = sSQL + " AND terminate is not null ";
sSQL = sSQL + " AND terminate < getdate() ";
SqlDataReader dr = moDb.GetDataReader(Cn, sSQL);
if(dr.HasRows) {bTerminate = true;}
dr.Dispose();

// loads disabled jobs also
sSQL = "SELECT j.*, terminate ";
sSQL = sSQL + " FROM tBackendJob j, tBackend b ";
sSQL = sSQL + " WHERE b.logname = 'KPI'";
sSQL = sSQL + " AND j.backendid = b.id";
sSQL = sSQL + " ORDER BY nextrun ";
dr = moDb.GetDataReader(Cn, sSQL);
while (dr.Read()) {
sName = dr["name"].ToString();
if (IsScheduled(sName)) {
oJob = moJobs[sName];
} else {
oJob =
(CJob)Activator.CreateInstance(Type.GetType(dr["class"].ToString() +
", KPI"));
oJob.ID = Convert.ToInt32(dr["id"]);
oJob.Name = dr["name"].ToString();
if (moDb.IsDate(dr["lastrun"].ToString())) {
// last time job successfully completed
oJob.LastRun = Convert.ToDateTime(dr["lastrun"]);
}
oJob.Enabled = Convert.ToInt32(dr["enabled"]);
sStatus = "DISABLED";
if(oJob.Enabled == 1) {sStatus = "ENABLED";}
moJobs.Add(sName, oJob);
sLoad = sLoad + "Loaded " + oJob.Name + " (" + sStatus
+ ")\r\n";
}
// user may have made changes
oJob.NextRun = Convert.ToDateTime(dr["nextrun"]);
oJob.RunPeriod = dr["runperiod"].ToString();
oJob.Retry = moDb.IIf(dr["retry"] == System.DBNull.Value,
"", dr["retry"].ToString());
oJob.RetryMinutes = Convert.ToInt32(dr["retryminutes"]);
oJob.Interval = Convert.ToInt32(dr["interval"]);
oJob.TimeOut = Convert.ToInt32(dr["timeoutminutes"]);
if(bTerminate) {
if (oJob.Enabled == 1)
{
oJob.Enabled = 0;
LogEvent(oJob.Name + " has been disabled due to
scheduled application termination.");
}
}
else
{
if (oJob.Enabled != Convert.ToInt32(dr["enabled"]))
{
oJob.Enabled = Convert.ToInt32(dr["enabled"]);
sStatus = "DISABLED";
if (oJob.Enabled == 1) { sStatus = "ENABLED"; }
LogEvent(oJob.Name + " is now " + sStatus);
}
}
}
dr.Dispose();
Cn.Dispose();
if(sLoad.Length > 0) {LogEvent(sLoad);}
}
catch (Exception e)
{
LogEvent(e.Message);
CErrTrap.Log(e, "Job: " + sName);
moAlert.Log(CBase.gsAlertAppError,
"KPI Collector Error",
"LoadSchedule\r\n" + e.Message, "");
}
}


Here is all the code in the RunJobs method:

private void RunJobs()
{
int iThreads = 0;
try
{
foreach (CJob oJob in moJobs.Values)
{
if(oJob.Enabled == 1) {
if (oJob.Running) {
iThreads++;
} else {
if (oJob.StartJob()) {
iThreads++;
StartJob(oJob);
}
}
if (iThreads >= 10) { break; }
}
}
}
catch (Exception e)
{
CErrTrap.Log(e);
LogEvent(e.Message);
moAlert.Log(CBase.gsAlertCriticalAppError, "Run Jobs
Error", e.Message, "");
}
}

private void StartJob(CJob poJob)
{
try
{
Thread oThread = new Thread(new
ThreadStart(poJob.Run));
oThread.Start(); // starts the job
}
catch (Exception e)
{
CErrTrap.Log(e);
moAlert.Log(CBase.gsAlertCriticalAppError, "Run Jobs
Error", e.Message, "");
}
}


public bool StartJob() {
string sSQL;
bool bStartJob = false;
try {
///////////////////
if(moDb.IsDate(this.Retry)) {
if(Convert.ToDateTime(this.Retry).CompareTo(DateTime.Now) < 0) {
bStartJob = true;
}
} else {
if(this.NextRun.CompareTo(DateTime.Now) < 0) {
bStartJob = true;
}
}
if(bStartJob) {
SqlConnection cnCCN = ConnectSqlDB(gsCCN_DB);
// time to run this job
sSQL = "UPDATE tBackendJob SET ";
sSQL = sSQL + " laststart = getdate()";
sSQL = sSQL + ", isrunning = 1";
sSQL = sSQL + " WHERE id = " + this.ID.ToString();
Execute(cnCCN, sSQL);

sSQL = " INSERT INTO tBackendJobRun ";
sSQL = sSQL + " (JobID, Thread, Started) ";
sSQL = sSQL + " VALUES (" + this.ID.ToString();
sSQL = sSQL + "," + moDb.CSQLStr(this.Name); // not required (for
ME II)
sSQL = sSQL + ",getdate())";
Execute(cnCCN, sSQL);

cnCCN.Dispose();
LogActivity("Job Start", this.Name, true, false);

}
////////////////////
return bStartJob;
}

catch (Exception e) {
CErrTrap.Log(e);
moAlert.Log(gsAlertAppError,
"KPI Collector Error", "StartJob\r\n" + e.Message, "");
}
return false;
}
 
R

rick

As Jon and Ignacio point out, you haven't really provided a complete  
question or problem description.

That said, just to clarify the basics: no, it is not possible for  
inheritance to cause trouble with respect to what value a given thread  
reads from a given instance of an object.

It certainly is possible for _other_ threading issues to exist, and those  
issues do often manifest themselves more frequently when you have multiple 
CPUs.  But in no case would that have something to do with object  
inheritance, as your subject line suggests you are asking about.

Pete

Thanks for the reply. I agree with you. I don't see how there could be
an issue. To appease him (he is not crazy, he is actually great to
work with), I added the following lines of code the CJob class.

private static Object moLock = new Object();
private bool mbRunning;

public bool Running {
get { lock(moLock) return mbRunning; }
set { mbRunning = value; }
}
 
R

rick

Rick,

Have a look at your timer, you will not be the first where the second
process start at almost the same time as the first, just because the Timer
ticking mechanism is not disabled and enabled and goes on and on and on and
then even is passing each other.

Cor

I don't think I have a timer issue the Thread Sleep.

public void Start()
{
Thread oRun = new Thread(new
ParameterizedThreadStart(Run));
oRun.Start(this);
}
public void Stop()
{
mbStop = true;
}
private static void Run(object poThread)
{
KPIServiceMain oService = poThread as KPIServiceMain;
while (!oService.mbStop)
{
try
{
if (!oService.mbRunning)
{
oService.mbRunning = true;
oService.RunJobs();
oService.miTicks--;
if (oService.miTicks <= 0)
{
oService.miTicks = 60;
oService.LogProcesses();
oService.UpdateAppHeartbeat();
oService.moJob.AlertTimedOutJobs();
oService.moAlert.Broadcast(); // send
alerts

oService.moAlert.ClearExpiredAlerts("KPI"); // clear expired alerts
oService.moAlert.SendTestAlert();
oService.LoadSchedule(); // reload
}
}
oService.mbRunning = false;
}
catch (Exception err)
{
CErrTrap.Log(err);
}
Thread.Sleep(1000);
}
}
 
R

rick

This sound remarkably similar to my service except I called the base class
Task instead of CJob.  I even have the Run method and Running property.


I suspect he is not crazy but is wrong. Somehow you or him is
misunderstanding what is going wrong. I would guess you have a reference to
Job2 when you expect Job1.

Michael

You are right. He is not crazy. He is a good guy. If Job2 never calls,
references, etc. Job1 or vice versa, there shouldn't be problem right?

I think he just wants to write something in his own style. One thing I
have learned after 10 years of programming is that my code is always
the best ;-). Lol.
 
R

rick

You've provided some code, but nothing we can really run.

Could you post a short but complete program which demonstrates the
problem?

Seehttp://www.pobox.com/~skeet/csharp/complete.htmlfor details of
what I mean by that.

I would be tough to post all the code that you could run. You wouldn't
have the databases for starters. Thanks for taking a look though. I
need all the help I can get.
 
P

Peter Duniho

[...]
It certainly is possible for _other_ threading issues to exist, and
those  
issues do often manifest themselves more frequently when you have
multiple  
CPUs.  But in no case would that have something to do with object  
inheritance, as your subject line suggests you are asking about.

Thanks for the reply. I agree with you. I don't see how there could be
an issue.

VERY IMPORTANT: I never wrote that there cannot be an issue. I wrote that
the inheritance of your classes would not cause an issue.

Those are two completely different things. You could have a bug that
causes you to read a value from the wrong instance. Those kinds of bugs
appear reasonably often in multi-threaded code. But should such a bug
exist, it wouldn't be because of the way you're inheriting a class.

If the code is thread-safe without any inheritance, it is thread-safe with
inheritance, as least as far as members of the base class are concerned
(you can always introduce new thread-safety problems by adding new code,
and of course creating an inherited class adds new code).

As a particular example: you have not posted any code that gives me
confidence that you've appropriately synchronized the "mbRunning" member,
which means that at least in the code you've posed, there is a possibility
that you have a race condition (i.e. the code is NOT thread-safe).

If you want to post an example of code that people can look at usefully,
you need to factor out the database aspects of your code (just remove it,
or stub it out with something that just sleeps for about the same amount
of time that a database operation might take), and post code that is
complete in the sense that no additional code is required in order to
compile and run it, but otherwise _only_ shows the threading aspect of
your code.

Otherwise, no one can tell you whether your code is thread-safe or not.

Pete
 
J

Jon Skeet [C# MVP]

I would be tough to post all the code that you could run. You wouldn't
have the databases for starters. Thanks for taking a look though. I
need all the help I can get.

Do you think the database side is actually relevant? I doubt it is. Try
cutting down more and more from the real code, until you've either
found the problem or you've got it in a postable state.
 
R

rick

 But should such a bug exist, it wouldn't be because of the way you're inheriting a class.

Makes sense to me. That is the statement I was looking for.
As a particular example: you have not posted any code that gives me  
confidence that you've appropriately synchronized the "mbRunning" member,  
which means that at least in the code you've posed, there is a possibility 
that you have a race condition (i.e. the code is NOT thread-safe).

What is a race condition? What is "appropriately synchronized?"
If you want to post an example of code that people can look at usefully

I thought I did that in the first post. In addition, every job class
sets the inherited CJob property to true when it starts, and to false
when it completes or errors. The following is an example. The StopRun
argument updates the job as successful or not. Am I missing something
else that you would need?

namespace KPI {
public class CSoloJob : CJob {
public override void Run() {
try {
StartRun(); // sets CJob Running property to true
// job code goes here
StopRun(true);
}
catch (Exception e) {
StopRun(false);
// logging goes here
}
}
}
 
P

Peter Duniho

Makes sense to me. That is the statement I was looking for.


What is a race condition? What is "appropriately synchronized?"

A "race condition" is when you've got two different threads accessing the
same data and the outcome depends solely on which thread gets to run first
or for longer. That is, it "wins the race".

Some race conditions are okay. For example, if you've got a queue that
multiple threads are adding data to, obviously only one thread can add
data at a time and even with proper synchronization it is possible that
the code will be designed such that the order of additions is determined
solely on random timing variability of the threads. This sort of race
condition is usually acceptable and isn't typically what people mean when
they talk about a race condition as a potential problem.

Other race conditions are not okay. For example, the situation I'm
talking about here is that you've got one thread that uses the "running"
state of a class to decide whether to start it running, but I see nothing
that suggests that you've synchronized access to that state. This means
there's the possibility that the one thread could start the thread based
on the "running" state being false, and before the object gets a chance to
set its own "running" state to true, execute that check again and try to
start running the object again.

In other words, the two threads are in a race. The thread that starts
jobs is racing with any thread it starts, and if it "wins the race" by
coming back to check that job again before the job's thread has had a
chance to update the "running" state, the thread that starts jobs is going
to try to start the job again, even though there's already a thread for
the job that was created.

You didn't post enough code for anyone to know for sure whether this race
condition really exists. So I'm not saying you _do_ have a race
condition. I'm just saying that based on what you've posted so far, you
_could_.
I thought I did that in the first post.

Unfortunately, thinking that you did isn't the same as doing so.
In addition, every job class
sets the inherited CJob property to true when it starts, and to false
when it completes or errors. The following is an example. The StopRun
argument updates the job as successful or not. Am I missing something
else that you would need?

Yes. We're missing quite a lot. As I already wrote, a correct sample
would have the properties that "no additional code is required in order to
compile and run it, but otherwise _only_ shows the threading aspect of
your code". So far, the code you've posted fails both tests.

For more details, please see Jon's nice write-up here:
http://www.yoda.arachsys.com/csharp/complete.html

Pete
 
R

rick

There are four files, 1) Form1, 2) CJob, 3) Job1 and 4) Job4. I think
this code should work. My partner believes that when a thread starts
on one CPU and finishes on another (context switching), there could be
a problem reading the correct object.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Forms;
using System.Threading;

namespace ThreadSample
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}

private Dictionary<string, CJob> moJobs;


private void RunJobs()
{
bool bRun = true;
while (bRun)
{
label3.Text = DateTime.Now.ToString();
label3.Refresh();
foreach (CJob oJob in moJobs.Values)
{
if (oJob.Enabled)
{
if (oJob.Running)
{
if (oJob.Name == "Job1") { label1.Text =
"Job1: Running"; }
if (oJob.Name == "Job2") { label2.Text =
"Job2: Running"; }
}
else
{
Thread oThread = new Thread(new
ThreadStart(oJob.Run));
oThread.Start(); // starts the job
if (oJob.Name == "Job1") { label1.Text =
"Job1: Started"; }
if (oJob.Name == "Job2") { label2.Text =
"Job2: Started"; }
}
label1.Refresh();
label2.Refresh();

}
Thread.Sleep(100);
}
}
}

private void Form1_Activated(object sender, EventArgs e)
{
moJobs = new Dictionary<string, CJob>();
LoadSchedule();
RunJobs();
}

public void LoadSchedule()
{
CJob oJob =
(CJob)Activator.CreateInstance(Type.GetType("ThreadSample.Job1,
ThreadSample"));
oJob.Name = "Job1";
oJob.NextRun = DateTime.Now;
oJob.Enabled = true;
moJobs.Add(oJob.Name, oJob);

oJob =
(CJob)Activator.CreateInstance(Type.GetType("ThreadSample.Job2,
ThreadSample"));
oJob.NextRun = DateTime.Now;
oJob.Enabled = true;
oJob.Name = "Job2";
moJobs.Add(oJob.Name, oJob);
}

private void Form1_Load(object sender, EventArgs e)
{

}

}


}


using System;
using System.Collections.Generic;
using System.Text;

namespace ThreadSample
{

public abstract class CJob
{
public abstract void Run(); // function for every job class

private bool mbRunning;
private DateTime mdNextRun;
private bool mbEnabled;
private string msName;

public bool Running
{
get { return mbRunning; }
set { mbRunning = value; }
}
public bool Enabled
{
get { return mbEnabled; }
set { mbEnabled = value; }
}
public DateTime NextRun
{
get { return mdNextRun; }
set { mdNextRun = value; }
}
public string Name
{
get { return msName; }
set { msName = value; }
}
public void StartRun()
{
mbRunning = true;
}
public void StopRun(bool pbSuccess)
{
mbRunning = false;
if(!pbSuccess) {
// log it
}
}

}
}


using System;
using System.Threading;

namespace ThreadSample
{
public class Job1 : CJob
{
public override void Run()
{
StartRun();
// job code goes here
Thread.Sleep(3000);
StopRun(true);
}
}
}

using System;

using System.Threading;

namespace ThreadSample
{
public class Job2 : CJob
{
public override void Run()
{
StartRun();
// job code goes here
Thread.Sleep(5000);
StopRun(true);
}

}
}
 
P

Peter Duniho

There are four files, 1) Form1, 2) CJob, 3) Job1 and 4) Job4. I think
this code should work.

Thanks. That's almost a great sample. It's still not complete, since it
can't be compiled standalone, but it's enough for inspection and getting a
decent understanding of the specific implementation you've got.
My partner believes that when a thread starts
on one CPU and finishes on another (context switching), there could be
a problem reading the correct object.

Well, that's almost certainly not an issue.

I don't see anything that would lead to CPU affinity in your code, so
having a thread winding up executing on more than one CPU over the
lifetime of the job should not cause a problem. More generally, you'd
have to specifically design your code with that sort of affinity in order
for it to be a potential issue. If you didn't intentionally create code
that depends on it running on a specific CPU, then it won't matter which
CPU is being used when it's executing, or even if the same CPU is used for
the lifetime of the thread.

It's one of those "if you don't know that this is specifically a problem,
then it probably isn't" things. And the "probably" only comes up with the
remote chance that someone would create thread affinity without fully
understanding the implications. It's an esoteric enough situation that
that's unlikely to happen. You're not going to stumble into by accident.

But you definitely do have the exact race condition that I described.
You've got nothing to protect the use of the "mbRunning" member field, and
so it's theoretically possible for the controlling thread to try to start
the same job more than once.

Because the code you posted includes a 100ms sleep between starting jobs,
this risk is reduced. But a) it's not eliminated, and b) it's hard to
know how much to rely on the exact architecture of the code you posted
because I'm hoping that the code you posted isn't really the way your
actual code works, as there are a number of other problems in it as well
(they mostly pertain to the use of the Forms classes, and I'm
guessing/hoping they exist as an artifact of you creating the sample,
rather than being something that's actually in your real code).

If the code is in fact pretty much identical to the real-world code you're
using, then you do have a number of other problems that need fixing as
well.

Pete
 
B

Ben Voigt [C++ MVP]

rick said:
There are four files, 1) Form1, 2) CJob, 3) Job1 and 4) Job4. I think
this code should work. My partner believes that when a thread starts
on one CPU and finishes on another (context switching), there could be
a problem reading the correct object.

Are you using thread local statics anywhere? If yes, then you very likely
have such a problem.
 
R

rick

My partner believes that when a thread starts
Well, that's almost certainly not an issue.

Good to know.
But you definitely do have the exact race condition that I described.  
You've got nothing to protect the use of the "mbRunning" member field, and 
so it's theoretically possible for the controlling thread to try to start  
the same job more than once.

Interesting. How do I protect Running? If the LoadSchedule only has
one instance of the job class, why wouldn't Running reflect false when
in fact it is set to true?

By the way, just some background. I am an old dog trying to learn new
tricks. I am the resident VB6 and ASP guru. Maybe that will help in
translation.

Because the code you posted includes a 100ms sleep between starting jobs,  this risk is reduced

Why do you say that?
(they mostly pertain to the use of the Forms classes 

You are right. I am not using a form. The app runs as a service. Below
is the entire service (without the database stuff).

using System;
using System.Collections.Generic;
using System.Text;
using DataLib;
using KPI;
using System.Collections;
using System.Data.SqlClient;
using System.Data;
using System.Threading;
using System.Diagnostics;

namespace KPIService
{

class KPIServiceMain
{
private Dictionary<string, CJob> moJobs;
private bool mbRunning;
private int miTicks = 60;

private bool mbStop = false;

public KPIServiceMain()
{
moJobs = new Dictionary<string, CJob>();
LoadSchedule(); // to run jobs
}
public void Start()
{
Thread oRun = new Thread(new
ParameterizedThreadStart(Run));
oRun.Start(this);
}
public void Stop()
{
mbStop = true;
}
private static void Run(object poThread)
{
KPIServiceMain oService = poThread as KPIServiceMain;
while (!oService.mbStop)
{
try
{
if (!oService.mbRunning)
{
oService.mbRunning = true;
oService.RunJobs();
oService.miTicks--;
if (oService.miTicks <= 0)
{
oService.miTicks = 60;
oService.LoadSchedule(); // reload
}
}
oService.mbRunning = false;
}
catch (Exception err)
{
CErrTrap.Log(err);
}
Thread.Sleep(1000);
}
}
public void LoadSchedule() {
// took out the database stuff here
dr = moDb.GetDataReader(Cn, sSQL);
while (dr.Read()) {
sName = dr["name"].ToString();
if (IsScheduled(sName)) {
oJob = moJobs[sName];
} else {
oJob =
(CJob)Activator.CreateInstance(Type.GetType(dr["class"].ToString() +
", KPI"));
oJob.ID = Convert.ToInt32(dr["id"]);
oJob.Name = dr["name"].ToString();
if (moDb.IsDate(dr["lastrun"].ToString())) {
// last time job successfully completed
oJob.LastRun =
Convert.ToDateTime(dr["lastrun"]);
}
oJob.Enabled = Convert.ToInt32(dr["enabled"]);
sStatus = "DISABLED";
if(oJob.Enabled == 1) {sStatus = "ENABLED";}
moJobs.Add(sName, oJob);
sLoad = sLoad + "Loaded " + oJob.Name + " (" +
sStatus + ")\r\n";
}
// user may have made changes
oJob.NextRun = Convert.ToDateTime(dr["nextrun"]);
oJob.RunPeriod = dr["runperiod"].ToString();
oJob.Retry = moDb.IIf(dr["retry"] ==
System.DBNull.Value, "", dr["retry"].ToString());
oJob.RetryMinutes =
Convert.ToInt32(dr["retryminutes"]);
oJob.Interval = Convert.ToInt32(dr["interval"]);
oJob.TimeOut =
Convert.ToInt32(dr["timeoutminutes"]);
if(bTerminate) {
if (oJob.Enabled == 1)
{
oJob.Enabled = 0;
LogEvent(oJob.Name + " has been disabled
due to scheduled application termination.");
}
}
else
{
if (oJob.Enabled !=
Convert.ToInt32(dr["enabled"]))
{
oJob.Enabled =
Convert.ToInt32(dr["enabled"]);
sStatus = "DISABLED";
if (oJob.Enabled == 1) { sStatus =
"ENABLED"; }
LogEvent(oJob.Name + " is now " +
sStatus);
}
}
}
dr.Dispose();
Cn.Dispose();
if(sLoad.Length > 0) {LogEvent(sLoad);}
}
private void RunJobs()
{
int iThreads = 0;
try
{
foreach (CJob oJob in moJobs.Values)
{
if(oJob.Enabled == 1) {
if (oJob.Running) {
iThreads++;
} else {
if (oJob.StartJob()) {
iThreads++;
StartJob(oJob);
}
}
if (iThreads >= 10) { break; }
}
}
}
catch (Exception e)
{
CErrTrap.Log(e);
LogEvent(e.Message);
moAlert.Log(CBase.gsAlertCriticalAppError, "Run Jobs
Error", e.Message, "");
}
}
private void StartJob(CJob poJob)
{
try
{
Thread oThread = new Thread(new
ThreadStart(poJob.Run));
oThread.Start(); // starts the job
}
catch (Exception e)
{
CErrTrap.Log(e);
moAlert.Log(CBase.gsAlertCriticalAppError, "Run Jobs
Error", e.Message, "");
}
}

}
}


If the code is in fact pretty much identical to the real-world code you're 
using, then you do have a number of other problems that need fixing as  
well.

I am interested. What needs fixing?

Thanks for the feedback.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top