Windows 服务 运行 但未执行代码
Windows Service is Running but not executing code
我们有 windows 服务 运行 很好,直到过程中出现任何异常。
它包含两个 Threads
(GenerateInvoice 和 GenerateReport)。
当我们的数据库服务器 CPU 使用率很高时,这些线程会被阻塞并导致类似死锁的情况。
我们已经对代码进行了一些更改以处理此类情况,例如在代码下方添加了 while 条件,但它仍然无法正常工作。
以下是 OnStart()
服务方法:
protected override void OnStart(string[] args)
{
try
{
log.Debug("Starting Invoice Generation Service");
_thread = new Thread(new ThreadStart((new GenerateInvoice()).Process));
_thread.IsBackground = true;
_thread.Start();
_reportThread = new Thread(new ThreadStart((new GenerateReport()).Process));
_reportThread.IsBackground = true;
_reportThread.Start();
}
catch (Exception ex)
{
log.Error("Error in Invoice Generation Service:", ex);
}
}
这里是第一个线程的处理代码:GenerateInvoice
public void Process()
{
while (isProcessActive)
{
try
{
DBBilling obj = new DBBilling();
DataTable dtInvoiceID = obj.readData(@"SELECT * FROM (SELECT ird.BillByType, ird.InvoiceID, ir.BeginDate, ir.EndDate, ir.SendToQB, ir.SendEmail,
i.ARAccountID, i.ARAccountHotelID, i.invoiceNumber,i.[STATUS],UPDATETIME,row_number() over (PARTITION BY ird.INVOICEID ORDER BY UPDATETIME DESC) AS row_number
FROM Invoices i JOIN InvoicesRunRequestDetails ird ON ird.InvoiceID=i.InvoiceID
JOIN InvoicesRunRequest ir ON ird.RequestID = ir.RequestID
Where i.[STATUS] = 'PENDING') AS rows
WHERE ROW_NUMBER=1 ORDER BY UPDATETIME");
processCounter = 0;
#region process
if (dtInvoiceID != null && dtInvoiceID.Rows.Count > 0)
{
//some code here..
}
#endregion
}
catch (Exception ex) //Mantis 1486 : WEBPMS1 Disk Space : 10 Aug 2016
{
log.ErrorFormat("Generate Invoice -> Process -> InnLink Billing Execute Query Exception. Error={0}", ex);
if(DBBilling.dbConnTimeoutErrorMessage.Any(ex.Message.Contains))
{
processCounter++;
if (processCounter >= 1) //Need to change to 25 after Problem Solve
{
isProcessActive = false;
log.ErrorFormat("Generate Invoice -> Process -> RunInvoice Service exiting loop"); //From here control is not going back
}
else
System.Threading.Thread.Sleep(5000); //Sleep for 5 Sec
}
}
}
}
第二个线程的处理,即 GenerateReport 代码:
public void Process()
{
AppSettingsReader ar = new AppSettingsReader();
string constr = (string)ar.GetValue("BillingDB", typeof(string));
SqlConnection con = new SqlConnection(constr);
while (isProcessActive)
{
try
{
DBBilling obj = new DBBilling();
DataTable dtReportRunID = obj.readData(@"SELECT ReportRunID,MonYear, BeginDate, EndDate FROM ReportRunRequest
Where [STATUS] = 'PENDING' ORDER BY ReportRunID");
processCounter = 0;
if (dtReportRunID != null && dtReportRunID.Rows.Count > 0)
{
//some code here..
}
}
catch (Exception ex) //Mantis 1486 : WEBPMS1 Disk Space : 10 Aug 2016
{
log.ErrorFormat("Generate Report -> Process -> InnLink Billing Execute Query Exception. Error={0}", ex);
if (DBBilling.dbConnTimeoutErrorMessage.Any(ex.Message.Contains))
{
processCounter++;
if (processCounter >= 1) //Need to change to 25 after Problem Solve
{
isProcessActive = false;
log.ErrorFormat("Generate Report -> Process -> RunInvoice Service Exiting loop"); //From here control is not going back
}
else
System.Threading.Thread.Sleep(5000); //Sleep for 5 Sec
}
}
}
}
避免这种情况的可能解决方案是什么?
避免它的方法是锁定对全局变量的每次访问,或者不使用全局变量。
这是一个明显的例子
DBBilling.dbConnTimeoutErrorMessage.Any(ex.Message.Contains)
dbConnTimeoutErrorMessage
是两个不同线程正在使用的静态字段,我认为它不是线程安全的,用
包围对它的访问
lock(locObj)
{
// access to dbConnTimeoutErrorMessage
}
我继续猜测 log
也是一个全局变量。也许甚至 isProcessActive
或 processCounter
。
我猜这些评论中还有更多内容 - 在将代码与两个不同的线程一起使用之前,请确保您的代码是线程安全的。
我怀疑锁定对我所说的内容的访问是否会解决您的问题,但我猜您在这些中缺乏线程安全编程是在需要时不使用 lock
的症状。秘诀是锁定对全局上下文的每次访问,仅此而已。
我的建议是使用 Timer 而不是无限循环,正如前面在其他答案中提到的,您需要某种同步。首先,您需要按如下方式实现在不同线程中使用的变量(我不知道您的变量的确切定义,但主要思想是在您的情况下使用 volatile 关键字):
public static volatile bool isProcessActive;
public static volatile int proccessCounter;
volatile 关键字关闭了在一个线程中使用变量的编译器优化。这意味着您的变量现在是线程安全的。
接下来您不需要使用 System.Threading.Timer
或 System.Timers.Timer
。我将在我的示例中使用第二个。
public sealed class GenerateInvoice :
{
protected const int timerInterval = 1000; // define here interval between ticks
protected Timer timer = new Timer(timerInterval); // creating timer
public GenerateInvoice()
{
timer.Elapsed += Timer_Elapsed;
}
public void Start()
{
timer.Start();
}
public void Stop()
{
timer.Stop();
}
public void Timer_Elapsed(object sender, ElapsedEventArgs e)
{
try
{
DBBilling obj = new DBBilling();
DataTable dtInvoiceID = obj.readData(@"SELECT * FROM (SELECT ird.BillByType, ird.InvoiceID, ir.BeginDate, ir.EndDate, ir.SendToQB, ir.SendEmail,
i.ARAccountID, i.ARAccountHotelID, i.invoiceNumber,i.[STATUS],UPDATETIME,row_number() over (PARTITION BY ird.INVOICEID ORDER BY UPDATETIME DESC) AS row_number
FROM Invoices i JOIN InvoicesRunRequestDetails ird ON ird.InvoiceID=i.InvoiceID
JOIN InvoicesRunRequest ir ON ird.RequestID = ir.RequestID
Where i.[STATUS] = 'PENDING') AS rows
WHERE ROW_NUMBER=1 ORDER BY UPDATETIME");
processCounter = 0;
#region process
if (dtInvoiceID != null && dtInvoiceID.Rows.Count > 0)
{
//some code here..
}
#endregion
}
catch (Exception ex) //Mantis 1486 : WEBPMS1 Disk Space : 10 Aug 2016
{
log.ErrorFormat("Generate Invoice -> Process -> InnLink Billing Execute Query Exception. Error={0}", ex);
if(DBBilling.dbConnTimeoutErrorMessage.Any(ex.Message.Contains))
{
processCounter++;
if (processCounter >= 1) //Need to change to 25 after Problem Solve
{
isProcessActive = false;
// supposing that log is a reference type and one of the solutions can be using lock
// in that case only one thread at the moment will call log.ErrorFormat
// but better to make synchronization stuff unside logger
lock (log)
log.ErrorFormat("Generate Invoice -> Process -> RunInvoice Service exiting loop"); //From here control is not going back
}
else
// if you need here some kind of execution sleep
// here you can stop timer, change it interval and run again
// it's better than use Thread.Sleep
// System.Threading.Thread.Sleep(5000); //Sleep for 5 Sec
}
}
}
}
对 GenerateReport 使用相同的方法,使 Timer
基于。
最后,您需要像这样更改 OnStart
和 OnStop
方法:
protected GenerateInvoice generateInvoice;
protected GenerateReport generateReport;
protected override void OnStart(string[] args)
{
// all exception handling should be inside class
log.Debug("Starting Invoice Generation Service");
generateInvoice = new GenerateInvoice();
generateInvoice.Start();
generateReport = new GenerateReport();
generateReport.Start();
}
protected override void OnStop()
{
generateInvoice.Stop();
generateReport.Stop();
}
我们有 windows 服务 运行 很好,直到过程中出现任何异常。
它包含两个 Threads
(GenerateInvoice 和 GenerateReport)。
当我们的数据库服务器 CPU 使用率很高时,这些线程会被阻塞并导致类似死锁的情况。
我们已经对代码进行了一些更改以处理此类情况,例如在代码下方添加了 while 条件,但它仍然无法正常工作。
以下是 OnStart()
服务方法:
protected override void OnStart(string[] args)
{
try
{
log.Debug("Starting Invoice Generation Service");
_thread = new Thread(new ThreadStart((new GenerateInvoice()).Process));
_thread.IsBackground = true;
_thread.Start();
_reportThread = new Thread(new ThreadStart((new GenerateReport()).Process));
_reportThread.IsBackground = true;
_reportThread.Start();
}
catch (Exception ex)
{
log.Error("Error in Invoice Generation Service:", ex);
}
}
这里是第一个线程的处理代码:GenerateInvoice
public void Process()
{
while (isProcessActive)
{
try
{
DBBilling obj = new DBBilling();
DataTable dtInvoiceID = obj.readData(@"SELECT * FROM (SELECT ird.BillByType, ird.InvoiceID, ir.BeginDate, ir.EndDate, ir.SendToQB, ir.SendEmail,
i.ARAccountID, i.ARAccountHotelID, i.invoiceNumber,i.[STATUS],UPDATETIME,row_number() over (PARTITION BY ird.INVOICEID ORDER BY UPDATETIME DESC) AS row_number
FROM Invoices i JOIN InvoicesRunRequestDetails ird ON ird.InvoiceID=i.InvoiceID
JOIN InvoicesRunRequest ir ON ird.RequestID = ir.RequestID
Where i.[STATUS] = 'PENDING') AS rows
WHERE ROW_NUMBER=1 ORDER BY UPDATETIME");
processCounter = 0;
#region process
if (dtInvoiceID != null && dtInvoiceID.Rows.Count > 0)
{
//some code here..
}
#endregion
}
catch (Exception ex) //Mantis 1486 : WEBPMS1 Disk Space : 10 Aug 2016
{
log.ErrorFormat("Generate Invoice -> Process -> InnLink Billing Execute Query Exception. Error={0}", ex);
if(DBBilling.dbConnTimeoutErrorMessage.Any(ex.Message.Contains))
{
processCounter++;
if (processCounter >= 1) //Need to change to 25 after Problem Solve
{
isProcessActive = false;
log.ErrorFormat("Generate Invoice -> Process -> RunInvoice Service exiting loop"); //From here control is not going back
}
else
System.Threading.Thread.Sleep(5000); //Sleep for 5 Sec
}
}
}
}
第二个线程的处理,即 GenerateReport 代码:
public void Process()
{
AppSettingsReader ar = new AppSettingsReader();
string constr = (string)ar.GetValue("BillingDB", typeof(string));
SqlConnection con = new SqlConnection(constr);
while (isProcessActive)
{
try
{
DBBilling obj = new DBBilling();
DataTable dtReportRunID = obj.readData(@"SELECT ReportRunID,MonYear, BeginDate, EndDate FROM ReportRunRequest
Where [STATUS] = 'PENDING' ORDER BY ReportRunID");
processCounter = 0;
if (dtReportRunID != null && dtReportRunID.Rows.Count > 0)
{
//some code here..
}
}
catch (Exception ex) //Mantis 1486 : WEBPMS1 Disk Space : 10 Aug 2016
{
log.ErrorFormat("Generate Report -> Process -> InnLink Billing Execute Query Exception. Error={0}", ex);
if (DBBilling.dbConnTimeoutErrorMessage.Any(ex.Message.Contains))
{
processCounter++;
if (processCounter >= 1) //Need to change to 25 after Problem Solve
{
isProcessActive = false;
log.ErrorFormat("Generate Report -> Process -> RunInvoice Service Exiting loop"); //From here control is not going back
}
else
System.Threading.Thread.Sleep(5000); //Sleep for 5 Sec
}
}
}
}
避免这种情况的可能解决方案是什么?
避免它的方法是锁定对全局变量的每次访问,或者不使用全局变量。
这是一个明显的例子
DBBilling.dbConnTimeoutErrorMessage.Any(ex.Message.Contains)
dbConnTimeoutErrorMessage
是两个不同线程正在使用的静态字段,我认为它不是线程安全的,用
lock(locObj)
{
// access to dbConnTimeoutErrorMessage
}
我继续猜测 log
也是一个全局变量。也许甚至 isProcessActive
或 processCounter
。
我猜这些评论中还有更多内容 - 在将代码与两个不同的线程一起使用之前,请确保您的代码是线程安全的。
我怀疑锁定对我所说的内容的访问是否会解决您的问题,但我猜您在这些中缺乏线程安全编程是在需要时不使用 lock
的症状。秘诀是锁定对全局上下文的每次访问,仅此而已。
我的建议是使用 Timer 而不是无限循环,正如前面在其他答案中提到的,您需要某种同步。首先,您需要按如下方式实现在不同线程中使用的变量(我不知道您的变量的确切定义,但主要思想是在您的情况下使用 volatile 关键字):
public static volatile bool isProcessActive;
public static volatile int proccessCounter;
volatile 关键字关闭了在一个线程中使用变量的编译器优化。这意味着您的变量现在是线程安全的。
接下来您不需要使用 System.Threading.Timer
或 System.Timers.Timer
。我将在我的示例中使用第二个。
public sealed class GenerateInvoice :
{
protected const int timerInterval = 1000; // define here interval between ticks
protected Timer timer = new Timer(timerInterval); // creating timer
public GenerateInvoice()
{
timer.Elapsed += Timer_Elapsed;
}
public void Start()
{
timer.Start();
}
public void Stop()
{
timer.Stop();
}
public void Timer_Elapsed(object sender, ElapsedEventArgs e)
{
try
{
DBBilling obj = new DBBilling();
DataTable dtInvoiceID = obj.readData(@"SELECT * FROM (SELECT ird.BillByType, ird.InvoiceID, ir.BeginDate, ir.EndDate, ir.SendToQB, ir.SendEmail,
i.ARAccountID, i.ARAccountHotelID, i.invoiceNumber,i.[STATUS],UPDATETIME,row_number() over (PARTITION BY ird.INVOICEID ORDER BY UPDATETIME DESC) AS row_number
FROM Invoices i JOIN InvoicesRunRequestDetails ird ON ird.InvoiceID=i.InvoiceID
JOIN InvoicesRunRequest ir ON ird.RequestID = ir.RequestID
Where i.[STATUS] = 'PENDING') AS rows
WHERE ROW_NUMBER=1 ORDER BY UPDATETIME");
processCounter = 0;
#region process
if (dtInvoiceID != null && dtInvoiceID.Rows.Count > 0)
{
//some code here..
}
#endregion
}
catch (Exception ex) //Mantis 1486 : WEBPMS1 Disk Space : 10 Aug 2016
{
log.ErrorFormat("Generate Invoice -> Process -> InnLink Billing Execute Query Exception. Error={0}", ex);
if(DBBilling.dbConnTimeoutErrorMessage.Any(ex.Message.Contains))
{
processCounter++;
if (processCounter >= 1) //Need to change to 25 after Problem Solve
{
isProcessActive = false;
// supposing that log is a reference type and one of the solutions can be using lock
// in that case only one thread at the moment will call log.ErrorFormat
// but better to make synchronization stuff unside logger
lock (log)
log.ErrorFormat("Generate Invoice -> Process -> RunInvoice Service exiting loop"); //From here control is not going back
}
else
// if you need here some kind of execution sleep
// here you can stop timer, change it interval and run again
// it's better than use Thread.Sleep
// System.Threading.Thread.Sleep(5000); //Sleep for 5 Sec
}
}
}
}
对 GenerateReport 使用相同的方法,使 Timer
基于。
最后,您需要像这样更改 OnStart
和 OnStop
方法:
protected GenerateInvoice generateInvoice;
protected GenerateReport generateReport;
protected override void OnStart(string[] args)
{
// all exception handling should be inside class
log.Debug("Starting Invoice Generation Service");
generateInvoice = new GenerateInvoice();
generateInvoice.Start();
generateReport = new GenerateReport();
generateReport.Start();
}
protected override void OnStop()
{
generateInvoice.Stop();
generateReport.Stop();
}