2016-09-21 1 views
0

Wir haben Windows-Dienst, der gut läuft, bis irgendwelche Ausnahmen im Prozess auftraten. Es enthält zwei Threads (GenerateInvoice und GenerateReport). Diese Threads werden blockiert und führen zu einer DeadLock-ähnlichen Situation, vor allem wenn die CPU-Auslastung auf unserem DataBase-Server hoch ist.Windows-Dienst läuft, führt aber keinen Code aus

Wir haben einige Änderungen im Code vorgenommen, um solche Situationen zu behandeln, wie hinzugefügt unter Bedingung unter Code aber immer noch es funktioniert nicht. Unten ist die OnStart() Methode der Dienstleistung:

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

Hier ist der Verarbeitungscode des ersten Thread: 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 
      } 
     }     
    }   
} 

Bearbeitung des zweiten Thread dh Gene Code:

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 
      } 
     } 
    } 
} 

was möglich Lösung, um solche Bedingungen zu vermeiden?

+0

nur Hintergrund-Threads Mit problematisch ist. Ich bin überrascht, dass der Dienst lange genug läuft, um etwas Nützliches zu erledigen. Es sollte kurz nach dem Start herunterfahren, da keine Vordergrundfäden vorhanden sind. –

+1

Warum verwenden Sie nicht einen 'Timer' außer der unbestimmten While-Schleife? Es ist keine gute Übung und kann Sie zu einigen Fehlern bringen. Und 'Thread.Sleep()' zu verwenden ist auch keine gute Übung. Sie sollten den Ruhezustand nur zum Debuggen verwenden. –

+1

@Damien_The_Unbeliever, wo ist kein Problem, Hintergrund-Threads im Dienst zu verwenden. Der Service funktioniert so lange, bis Sie ihn stoppen. Es ist gut, in Windows-Hintergrundthreads zu verwenden. In diesem Fall können Sie Ihren Dienst problemlos beenden. –

Antwort

0

Ich schlage vor, Timer anstelle von Endlosschleife zu verwenden, und wie bereits in anderen Antworten erwähnt, benötigen Sie eine Art von Synchronisation. Zunächst einmal müssen Sie die Variablen, die in verschiedenen Threads verwendet implementieren, wie folgt (ich weiß nicht genau Definitionen Ihrer Variablen, aber Leitgedanke ist flüchtig Schlüsselwort in Ihrem Fall zu verwenden):

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

volatil Schlüsselwort schaltet die Compileroptimierungen für die Verwendung von Variablen in einem Thread aus. Dies bedeutet, dass Ihre Variablen jetzt Thread-sicher sind.

Als nächstes müssen Sie weder System.Threading.Timer noch System.Timers.Timer verwenden. Ich werde in meinem Beispiel den zweiten verwenden.

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 
      } 
     }      
    } 
} 

den gleichen Ansatz verwenden für die Gene Timer -Basis zu machen.

Und schließlich müssen Sie Ihre OnStart und OnStop Methoden so etwas wie dies ändern:

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

Vielen Dank! Ihre Lösung ist sehr hilfreich. Markieren Sie es als Antwort! –

+0

mit einem Timer ist ein guter Weg zu gehen. Das Markieren Ihrer Felder als volatil ist eine schlechte Übung und wird abgeraten. Es macht nicht nur Ihre Felder threadsafe, es bedeutet, dass es versuchen wird, Ihnen den aktuellsten Wert zu bringen und andere Threads in diesem Prozess anzuhalten. Das Sperren Ihres Zugangs ist eine viel bessere Garantie. http://stackoverflow.com/a/17530556/3090249 – gilmishal

1

Die Möglichkeit, dies zu vermeiden, besteht darin, jeden Zugriff auf eine globale Variable zu sperren oder keine globalen Variablen zu verwenden.

hier ist ein offensichtliches Beispiel

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

dbConnTimeoutErrorMessage ein statisches Feld ist, das von zwei verschiedenen Threads verwendet wird, und ich nehme nicht sicher, surround Zugriff darauf ist Thread mit einem

lock(locObj) 
{ 
    // access to dbConnTimeoutErrorMessage 
} 

Ich gehe weiter und denke, dass log auch eine globale Variable ist. Vielleicht vielleicht sogar isProcessActive oder processCounter.

Ich vermute, es gibt mehr in diesen Kommentaren - stellen Sie sicher, dass Ihr Code threadsafe ist, bevor Sie es mit zwei verschiedenen Threads verwenden.

Ich bezweifle, Locking-Zugriff auf das, was ich sagte, wird Ihr Problem beheben, aber ich denke, Ihr Mangel an threadsafe Programmierung in diesen ist ein Symptom, lock nicht zu verwenden, wenn es benötigt wird. Das Geheimnis ist, jeden Zugriff auf einen globalen Kontext zu sperren, und genau das.

+0

Danke für den Vorschlag! Ich werde versuchen, dies umzusetzen. Was ist 'locObj' in meinem Fall? –

+0

lockObj ist ein einfaches, statisches Objekt (es muss nicht statisch sein, aber es muss für die zwei verschiedenen Threads existieren) - initialisiere es einfach mit 'new object()'. Stellen Sie sicher, dass Sie für verschiedene globale Variablen ein unterschiedliches lockObject verwenden. gehen Sie zu https://msdn.microsoft.com/en-us/library/mt679037.aspx für weitere Informationen – gilmishal

Verwandte Themen