我们有正常运行的Windows服务,直到流程中出现任何异常。
它包含两个Threads
(GenerateInvoice和GenerateReport)。
这些线程被阻塞并导致DeadLock状态,主要是在我们的DataBase服务器上CPU使用率很高时。
我们在代码中做了一些更改来处理这样的情况,例如在代码下面添加了代码,但仍然无法正常工作。
以下是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
}
}
}
}
避免这种情况的可能解决方案是什么?
答案 0 :(得分:1)
避免它的方法是锁定对全局变量的每次访问,或者不使用全局变量。
这是一个明显的例子
nr()
DBBilling.dbConnTimeoutErrorMessage.Any(ex.Message.Contains)
是一个静态字段,正在从两个不同的线程中使用,我认为它不是线程安全的,使用
dbConnTimeoutErrorMessage
我要继续猜测lock(locObj)
{
// access to dbConnTimeoutErrorMessage
}
也是一个全局变量。甚至可能是log
或isProcessActive
。
我猜这些评论中还有更多内容 - 在使用两个不同的线程之前,请确保您的代码是线程安全的。
我怀疑锁定访问我所说的将解决你的问题,但我猜你缺乏线程安全编程是在需要时不使用processCounter
的症状。秘诀是锁定对全局上下文的每次访问,就这样。
答案 1 :(得分:0)
我建议使用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();
}