Multithreading bugs are very difficult to reproduce & debug. Thorough debugging is the only way to track the bug.
What I can infer from your code (as I don't know all the details of it and most probably I'm wrong), You are running two separate thread with these thread function : updateDeliveryReportsDB() & sendMessage() The first issue I can see is, sendMessage() uses lock(gate) but updateDeliveryReportsDB() uses lock(gate2). Why it is like that ? If in the application *only* two thread is present and neither depends on another one, then you don't need that lock. Because each thread is looping infinity, and checking this lock will slow it and it's not needed. It's like reading two disjoint data, you don't need any synchronization for reading disjoint data. lock() is needed when multiple thread depends on *same* critical section. Do you think using lock(gate) in both place will solve the issue ??? Ask yourself. Another place you need to check whether sendMessage() logs the sent/ unsent item properly or not and you are getting correct data in updateDeliveryReportsDB(). Put a long sleep() in sendMessage() after sending a sms and add breakpoint in updateDeliveryReportsDB() Good luck and let us know the your findings. On Jun 8, 3:51 am, programmer <[email protected]> wrote: > Hello, > > I am writing a multi-threaded C# console program that > is executed by a windows Service which acts as a Service Control > Manager > for the console app and thereby executes the application > as a Service. > > For the Service to run the apps as a Service, they have to be written > a certain way. > The Service works by starting up a process for the application, before > executing the app code. > > On entering the main method of the app, it tests for a Mutex and as > long as there is already a mutex when the service was started, the > conditions will make the app run endlessly unless the controlling > service is stopped. > > The main issue is that I have been having some unexpected results in > the execution of the app. There are 2 threads running concurrently but > sometimes it seems like one thread is smothered or deprived of > resources for execution. > > Here is the source code. > > namespace BulkSmsClient > { > class Program > { > static Mutex mSingleton; > private static readonly ILog log = > LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().Declar > ingType); > public static Thread T1; > public static Thread T2; > private static object gate = new object(); > private static object gate2 = new object(); > //BELOW THE VARIABLE FOR MANAGING THIS AS SERVICE USING > PROCESS MANAGER (VAS SYSTEM MANAGER)// REMOVE IF NOT NEEDED > private static bool m_isApplicationTerminating; > //END > > static void Main(string[] args) > { > try > { > //CAUTION: UNCOMMENT THE MUTEX TEST CODE BLOCK BELOW > IF YOU ARE NOT USING THE PROCESS MANAGER SERVICE. > //Check if there is already an instance of the > application running > //Kill second instance if true > //REQUIRES reference to the System.Threading namespace > //bool created; > //mSingleton = new Mutex(true, "BulkSmsClient", out > created); > //if (!created) > //{ > // Console.WriteLine("Already running, exiting"); > // return; > //} > > //Configure log4net passing in the log4net.xml config > file at the Application root folder. > XmlConfigurator.Configure(new > System.IO.FileInfo(System.Reflection.Assembly.GetExecutingAssembly().Locati > on.Remove(System.Reflection.Assembly.GetExecutingAssembly().Location.LastIn > dexOf("\ > \BulkSmsClient.exe")) + "\\log4net.xml")); > > //// log4net configuration in the app.config file here > for configuring this to log to the PROCESS MANAGER's Global log file > //string appId = String.Format(@"{0}\{1}.exe", > Environment.CurrentDirectory, > // > Assembly.GetExecutingAssembly().GetName().Name); > > //XmlConfigurator.Configure(); > //log4net.GlobalContext.Properties["identity"] = > appId; > > log.Info("Entering Application..."); > > log.Debug("Initialising ServicePointManager to use the > Certificate Policy..."); > //DEPRECATED: Get the ServicePointManager to use the > Certificate Policy. This allows you to use HTTPS with self-signed > certificates > //System.Net.ServicePointManager.CertificatePolicy = > new TrustAllCertificatePolicy(); > > // CURRENT: allows for validation of SSL conversations > //Requires the following imports: > //using System.Net.Security; > //using System.Security.Cryptography.X509Certificates; > //and the Callback method below: > ValidateRemoteCertificate > //TODO: Separate into a Class > > System.Net.ServicePointManager.ServerCertificateValidationCallback += > new RemoteCertificateValidationCallback( > ValidateRemoteCertificate > ); > > //Retrieving the AppSettings Section of the > Configuration file > NameValueCollection nvcAllAppSettings = > ConfigurationManager.AppSettings; > > //BELOW THE VARIABLES FOR MANAGING THIS AS SERVICE > USING PROCESS MANAGER (VAS SYSTEM MANAGER)// REMOVE IF NOT NEEDED > Mutex mutex = null; > bool isFirstInstance = true; > m_isApplicationTerminating = false; > //END > > log.Debug("Enter Main method"); > > log.Debug(string.Format("Main Thread Id {0}", > Thread.CurrentThread.ManagedThreadId.ToString())); > > T1 = new Thread(new > ParameterizedThreadStart(sendMessage)); > T2 = new Thread(new > ParameterizedThreadStart(updateDeliveryReportsDB)); > > T1.Start(nvcAllAppSettings); > T2.Start(nvcAllAppSettings); > > // Continue whilst there are active threads > while (!m_isApplicationTerminating) > { > > if (!m_isApplicationTerminating) > { > //mutex = new Mutex(false, > Environment.CurrentDirectory, out isFirstInstance); > string mutexname = > String.Format(@"{0}\{1}.exe", Environment.CurrentDirectory, > Assembly.GetExecutingAssembly().GetName().Name); > mutex = new Mutex(false, > mutexname.Replace(@"\", String.Empty), out isFirstInstance); > } > if (isFirstInstance) > { > m_isApplicationTerminating = true; > } > else > { > if (mutex != null) > { > mutex.Close(); > } > //Thread.Sleep(1000); > } > } > > log.Debug("Exit Main method"); > > } > catch (Exception ex) > { > log.Error(ex.Message); > } > } > > private static void sendMessage(object m_nvcAllAppSettings) > { > while (!m_isApplicationTerminating) > { > log.Debug(string.Format("Inside SendMessage Thread > with Thread Id {0}", > Thread.CurrentThread.ManagedThreadId.ToString())); > > NameValueCollection nvcAllAppSettings = > (NameValueCollection)m_nvcAllAppSettings; > > //Instantiate Web Service > try > { > > int i = 0; > string strVendor = String.Empty; > //DateTime datStartTime; > //DateTime datEndTime; > Service messageController; > ClientMessageResult result; > ClientInfo client = null; > ClientMessage message; > service1 retrieveTransLog; > TransactionLog[] transactionlog; > string[] strGuidResult; > string messageId = string.Empty; > //TransactionScope transaction = null; > > //Initialising the A3BulkSmsDeliveryReports > WebService > retrieveTransLog = new service1(); > > log.Debug("Connecting to host: " + > BulkSmsClient.Properties.Settings.Default.BulkSmsClient_org_dyndns_a3ando_S > ervice); > messageController = new Service(); > log.Debug("Connected successfully to host: " + > BulkSmsClient.Properties.Settings.Default.BulkSmsClient_org_dyndns_a3ando_S > ervice); > > log.Debug("Reading data from the messaging source > table - Sms2Vendor."); > //Read data from the messaging source table - > Sms2Vendor. > DataTable dtDueSms = > Sms2Vendor.GetDueSms2Vendor(); > > result = null; > client = new ClientInfo(); > message = new ClientMessage(); > > string strMessageBody = String.Empty; > > // My-Id will be replaced by a Client Id supplied > by A3&O > client.ClientId = nvcAllAppSettings["ClientId"]; > > // My-Key will be replaced by a Client Key > supplied by A3&O > client.ClientKey = > nvcAllAppSettings["Authorization"]; > > //client.ClientName = @"ADMIN"; > client.ClientName = > nvcAllAppSettings["ClientName"]; > > if (dtDueSms.Rows.Count > 0) > { > for (int m = 0; m < dtDueSms.Rows.Count; m++) > { > //Construct message > > strMessageBody = dtDueSms.Rows[m] > ["Message"].ToString(); > > message.Header = (! > string.IsNullOrEmpty(dtDueSms.Rows[m]["SenderId"].ToString())) ? > dtDueSms.Rows[m]["SenderId"].ToString() : nvcAllAppSettings["Header"]; > message.Body = strMessageBody; > message.DestinationAddress = > dtDueSms.Rows[m]["PhoneNo"].ToString(); > > //Set other parameters > strVendor = (! > string.IsNullOrEmpty(dtDueSms.Rows[m]["Vendor"].ToString())) ? > dtDueSms.Rows[m]["Vendor"].ToString() : nvcAllAppSettings["Vendor"]; > > //Get the time before the Server submit is > done > //string strStartTime = > retrieveTransLog.GetServerDateTime(); > > //Submit the message. Send it to the phone > and log the transaction. > log.Debug("Sending the sms"); > result = > messageController.SubmitMessage(client, message); > log.Debug("Completed Sending the sms. > Status: " + result.Result.ToString()); > //Thread.Sleep(5000); > > //Extract guid > if (result.Result.ToString().ToUpper() == > "OK") > { > strGuidResult = > result.Message.ToString().Split(':'); > messageId = strGuidResult[2].Trim(); > } > else > { > messageId = null; > } > > //NEED TO TEST THAT Result returns OK here > or else return from method > if (result != null) > { > if (result.Result.ToString().ToUpper() > == "OK") > { > > //Mark this scope as transactional > and starting a transaction > //transaction = new > TransactionScope(); > > lock (gate) > { > //Update the tables to > indicate that message has been sent but not yet delivered > i = > Sms2Vendor.UpdateSentButUndelivrdSms(dtDueSms.Rows[m] > ["AutoID"].ToString(), dtDueSms.Rows[m]["PhoneNo"].ToString(), > dtDueSms.Rows[m]["Message"].ToString(), strVendor, null, > DateTime.Parse(dtDueSms.Rows[m]["DateSent"].ToString()), null, 5, > null, message.Header, messageId, dtDueSms.Rows[m] > ["Msg_Type"].ToString()); > } > > //Commit the transaction > //transaction.Complete(); > //Dispose the transaction object > //transaction.Dispose(); > } > else > { > //Log the errorneous > lock (gate) > { > //Update the tables to > indicate that message has been sent but not yet delivered > > i = > Sms2Vendor.UpdateSentButUndelivrdSms(dtDueSms.Rows[m] > ["AutoID"].ToString(), dtDueSms.Rows[m]["PhoneNo"].ToString(), > dtDueSms.Rows[m]["Message"].ToString(), strVendor, null, > DateTime.Parse(dtDueSms.Rows[m]["DateSent"].ToString()), null, 5, > null, message.Header, messageId, dtDueSms.Rows[m] > ["Msg_Type"].ToString()); > } > > log.Debug(result.Result.ToString().ToUpper()); > } > } > > transactionlog = > retrieveTransLog.GetTransactionLog(client.ClientId, messageId); > > if (result != null) > { > if (result.Result.ToString().ToUpper() > == "OK") > { > if (transactionlog.Length > 0) > { > //Update the messaging source > table - Sms2Vendor//Insert into the Report table - VendorSmsReport > if > (transactionlog[0].Status.ToLower().Trim() == "delivrd") > { > log.Debug("Sms was > DELIVERED. Updating the Sms2Vendor and VendorSmsReport tables"); > //To add more > transactional support for the two statements below > > //Mark this scope as > transactional and starting a transaction > //transaction = new > TransactionScope(); > > lock (gate) > { > > i = > Sms2Vendor.UpdateDelivrdSms(dtDueSms.Rows[m]["AutoID"].ToString(), > message.DestinationAddress, message.Body, strVendor, null, > DateTime.Parse(dtDueSms.Rows[m]["DateSent"].ToString()), > transactionlog[0].Status, 1, > DateTime.Parse(transactionlog[0].DeliveryDateTime), message.Header, > messageId, dtDueSms.Rows[m]["Msg_Type"].ToString()); > } > > //Commit the transaction > //transaction.Complete(); > > //End this transaction > scope by disposing the transaction object > //transaction.Dispose(); > > log.Debug("Update > complete"); > } > else > { > log.Debug("Sms NOT YET > DELIVERED. Updating the Sms2Vendor and VendorSmsReport tables"); > > //Mark this scope as > transactional and starting a transaction > //transaction = new > TransactionScope(); > > lock (gate) > { > > i = > Sms2Vendor.UpdateDelivrdSms(dtDueSms.Rows[m]["AutoID"].ToString(), > message.DestinationAddress, message.Body, strVendor, null, > DateTime.Parse(dtDueSms.Rows[m]["DateSent"].ToString()), > transactionlog[0].Status, 5, null, message.Header, messageId, > dtDueSms.Rows[m]["Msg_Type"].ToString()); > } > > //Commit the transaction > //transaction.Complete(); > > //End this transaction > scope by disposing the transaction object > //transaction.Dispose(); > > log.Debug("Update > complete"); > } > } > > //Console.WriteLine("OK"); > log.Debug("OK"); > } > else > { > > log.Debug(result.Result.ToString().ToUpper()); > } > } > // Sleep after each iteration > Thread.Sleep(1000); > } > > }//Comment out the else block below to execute the > Application indefinitely or forever. > else > { > Thread.Sleep(1000); > } > > //} > } > catch (Exception ex) > { > log.Error(ex.Message); > } > > log.Debug(string.Format("Exiting SendMessage Thread > with Thread Id {0}", > Thread.CurrentThread.ManagedThreadId.ToString())); > } > } > > private static void updateDeliveryReportsDB(object > m_nvcAllAppSettings) > { > while (!m_isApplicationTerminating) > { > log.Debug(string.Format("Inside > UpdateDeliveryReportsDB Thread with Thread Id {0}", > Thread.CurrentThread.ManagedThreadId.ToString())); > > NameValueCollection nvcAllAppSettings = > (NameValueCollection)m_nvcAllAppSettings; > > try > { > > //int i = 0; > string strVendor = String.Empty; > //DateTime datStartTime; > //DateTime datEndTime; > //Service messageController; > //ClientMessageResult result; > ClientInfo client = new ClientInfo(); ; > //ClientMessage message; > service1 retrieveTransLog = new service1(); ; > TransactionLog[] transactionlog; > //TransactionScope transaction = null; > > //Loop through Database and update Delivery reports > DataTable dtGetTop10UndeliveredSms = > Sms2Vendor.GetTop10UndeliveredSms(nvcAllAppSettings["Vendor"]); > > if (dtGetTop10UndeliveredSms.Rows.Count > 0) > { > for (int j = 0; j < > dtGetTop10UndeliveredSms.Rows.Count; j++) > { > > if (client == null) > { > client = new ClientInfo(); > } > > client.ClientId = (! > string.IsNullOrEmpty(client.ClientId)) ? client.ClientId : > nvcAllAppSettings["ClientId"]; > > if (retrieveTransLog == null) > { > retrieveTransLog = new service1(); > } > > transactionlog = > retrieveTransLog.GetTransactionLog(client.ClientId, > dtGetTop10UndeliveredSms.Rows[j]["MessageId"].ToString()); > > //Update the local Database tables > if ((transactionlog.Length > 0) && > (transactionlog[0].Status.ToLower().Trim() == "delivrd")) > { > //We have a match > //Update the Sms2Vendor table > > log.Debug("Sms was DELIVERED. Updating > the Sms2Vendor and VendorSmsReport tables"); > //Mark this scope as transactional and > starting a transaction > //transaction = new > TransactionScope(); > > lock (gate2) > { > > int k = > Sms2Vendor.UpdateDelivrdSms(dtGetTop10UndeliveredSms.Rows[j] > ["AutoID"].ToString(), dtGetTop10UndeliveredSms.Rows[j] > ["PhoneNo"].ToString(), dtGetTop10UndeliveredSms.Rows[j] > ["Message"].ToString(), dtGetTop10UndeliveredSms.Rows[j] > ["Vendor"].ToString(), null, > DateTime.Parse(dtGetTop10UndeliveredSms.Rows[j] > ["DateSent"].ToString()), transactionlog[0].Status, 1, > DateTime.Parse(transactionlog[0].DeliveryDateTime), > dtGetTop10UndeliveredSms.Rows[j]["SenderId"].ToString(), > dtGetTop10UndeliveredSms.Rows[j]["MessageId"].ToString(), > dtGetTop10UndeliveredSms.Rows[j]["Msg_Type"].ToString()); > } > > //Commit the transaction > //transaction.Complete(); > > //End this transaction scope by > disposing the transaction object > //transaction.Dispose(); > > log.Debug("Update complete"); > } > > //Sleep after each iteration > Thread.Sleep(1000); > } > } > } > catch(Exception ex) > { > log.Error(ex.Message); > } > log.Debug(string.Format("Exiting > UpdateDeliveryReportsDB Thread with Thread Id {0}", > Thread.CurrentThread.ManagedThreadId.ToString())); > } > }
