Windows服务正在运行但未执行代码

时间:2016-09-21 07:44:46

标签: c# multithreading windows-services

我们有正常运行的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
            }
        }
    }
}

避免这种情况的可能解决方案是什么?

2 个答案:

答案 0 :(得分:1)

避免它的方法是锁定对全局变量的每次访问,或者不使用全局变量。

这是一个明显的例子

nr()

DBBilling.dbConnTimeoutErrorMessage.Any(ex.Message.Contains)是一个静态字段,正在从两个不同的线程中使用,我认为它不是线程安全的,使用

进行环绕声访问
dbConnTimeoutErrorMessage

我要继续猜测lock(locObj) { // access to dbConnTimeoutErrorMessage } 也是一个全局变量。甚至可能是logisProcessActive

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

我怀疑锁定访问我所说的将解决你的问题,但我猜你缺乏线程安全编程是在需要时不使用processCounter的症状。秘诀是锁定对全局上下文的每次访问,就这样。

答案 1 :(得分:0)

我建议使用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();
}