2016-09-21 110 views
0

我們有windows服務運行正常,直到進程中發生任何異常。 它包含兩個Threads(GenerateInvoice和GenerateReport)。 當我們的DataBase服務器上的CPU使用率很高時,這些線程會越來越多地被阻塞並導致類似於DeadLock的情況。Windows服務正在運行但沒有執行代碼

我們已經對代碼進行了一些更改,以處理類似條件下添加的情況,但仍然無法正常工作。 下面是服務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

爲什麼不使用'Timer'除了無限循環?這不是很好的做法,會給你帶來一些錯誤。並且使用'Thread.Sleep()'也不是很好的做法。您應該僅將睡眠用於調試目的。 –

+1

@Damien_The_Unbeliever,在服務中使用後臺線程沒有問題。服務將工作,直到你將停止它。在windows服務後臺線程中使用是很好的。在這種情況下,您可以輕鬆地停止您的服務。 –

回答

0

我建議使用Timer而不是無限循環,正如前面在其他答案中提到的那樣,您需要某種同步。首先,你需要實現你的變量,在不同的線程中使用如下(我不知道你的變量的準確定義,但主要的想法是你的情況使用volatile關鍵字):

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

揮發性關鍵字會關閉在一個線程中使用變量的編譯器優化。這意味着你的變量現在是線程安全的。

接下來您既不需要使用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(); 
} 
+0

謝謝!你的解決方案真的很有用。將其標記爲答案!使用計時器的 –

+0

是一個好方法。標記你的領域是不穩定的,這是一個不好的做法,並且不鼓勵。這不是簡單地讓你的字段線程安全,它意味着它會嘗試給你帶來最新的值,並且可能會暫停進程中的其他線程。鎖定您的訪問是一個更好的保證。 http://stackoverflow.com/a/17530556/3090249 – gilmishal

1

避免它的方法是鎖定每個對全局變量的訪問,或者不要使用全局變量。

這裏是一個明顯的例子

DBBilling.dbConnTimeoutErrorMessage.Any(ex.Message.Contains)

dbConnTimeoutErrorMessage是正在從兩個不同的線程中使用一個靜態字段,我以爲是不是線程安全的,環繞的訪問將其與

lock(locObj) 
{ 
    // access to dbConnTimeoutErrorMessage 
} 

我會繼續前進,猜測log也是一個全局變量。也許甚至可能是isProcessActiveprocessCounter

我猜這些評論中有更多 - 在使用兩個不同的線程之前,確保你的代碼是線程安全的。

我懷疑鎖定訪問我所說的會解決您的問題,但我想你在這些缺乏線程安全編程是一個症狀,不需要時使用lock。祕訣在於鎖定每個訪問全局上下文,就這樣。

+0

感謝您的建議!我會盡力實現這一點。我的情況是什麼'locObj'? –

+0

lockObj是一個簡單的靜態對象(它不一定是靜態的,但它必須存在於兩個不同的線程中) - 只需用'new object()'初始化它即可。確保爲不同的全局變量使用不同的lockObject。 請訪問https://msdn.microsoft.com/en-us/library/mt679037.aspx獲取更多信息 – gilmishal