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 也是一个全局变量。也许甚至 isProcessActiveprocessCounter

我猜这些评论中还有更多内容 - 在将代码与两个不同的线程一起使用之前,请确保您的代码是线程安全的。

我怀疑锁定对我所说的内容的访问是否会解决您的问题,但我猜您在这些中缺乏线程安全编程是在需要时不使用 lock 的症状。秘诀是锁定对全局上下文的每次访问,仅此而已。

我的建议是使用 Timer 而不是无限循环,正如前面在其他答案中提到的,您需要某种同步。首先,您需要按如下方式实现在不同线程中使用的变量(我不知道您的变量的确切定义,但主要思想是在您的情况下使用 volatile 关键字):

public static volatile bool isProcessActive;
public static volatile int proccessCounter;

volatile 关键字关闭了在一个线程中使用变量的编译器优化。这意味着您的变量现在是线程安全的。

接下来您不需要使用 System.Threading.TimerSystem.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 基于。

最后,您需要像这样更改 OnStartOnStop 方法:

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();
}