public static bool MakeThread(IODThread threadClass, bool isAutoStart = false, string groupName = "") { if (threadClass.IsInit) { return(false); } ODThread thread = new ODThread(threadClass.GetThreadRunIntervalMS(), threadClass.OnThreadRun); thread.Name = threadClass.GetThreadName(); if (groupName != "") { thread.GroupName = groupName; } thread.AddExceptionHandler(new ExceptionDelegate(threadClass.OnThreadException)); thread.AddExitHandler(new WorkerDelegate(threadClass.OnThreadExit)); if (isAutoStart) { thread.Start(); } threadClass.IsInit = true; threadClass.ODThread = thread; return(true); }
///<summary>Worker method for _odThreadEServices. Call StartEServiceMonitoring() to start monitoring eService signals instead of calling this method directly.</summary> private void ProcessEServiceSignals(ODThread odThread) { //-------------------------------------------------------------------------------------------------------------------------------------------- //This method can get enhanced later to be more generic like ProcessSignals where it grabs a list of eService signals and loops through them. //For now, we only care about the status of the Listener Service and I want the first edition of this method to be as light as possible. //-------------------------------------------------------------------------------------------------------------------------------------------- Color colorCur=_colorEServicesBackground; //The listener service will have a local heartbeat every 5 minutes so it's overkill to check every time timerSignals_Tick fires. //Only check the Listener Service status once a minute. //The downside to doing this is that the menu item will stay red up to one minute when a user wants to stop monitoring the service. eServiceSignalSeverity listenerStatus=EServiceSignals.GetServiceStatus(eServiceCode.ListenerService); if(listenerStatus==eServiceSignalSeverity.None) { //This office has never had a valid listener service running and does not have more than 5 patients set up to use the listener service. //Quit the thread so that this computer does not waste its time sending queries to the server every minute. odThread.QuitAsync(); return; } if(listenerStatus==eServiceSignalSeverity.Critical) { _colorEServicesBackground=COLOR_ESERVICE_ALERT_BACKGROUND; } else { _colorEServicesBackground=SystemColors.Control; } if(colorCur!=_colorEServicesBackground) { //Since the status changed, redraw the eServices menu item. BeginInvoke(new InvalidateEServicesMenuItemDelegate(InvalidateEServicesMenuItem)); } }
///<summary>Stops the eService monitoring thread and sets the eServices menu item colors to a disabled state because the Log On window will be shown next.</summary> private void StopEServiceMonitoring() { if(_odThreadEServices==null) { return;//Nothing to do, the service was already stopped. } //QuitSync the thread just because it has the power to live for up to a minute which is unnecessary. //There is no reason to wait more than 1 second for the thread to quit. _odThreadEServices.QuitSync(1000); _odThreadEServices=null; //Set the background color of the menu item back to gray just in case it was red for the last user that was logged in. _colorEServicesBackground=SystemColors.Control; InvalidateEServicesMenuItem();//No need to invoke this method, we should always be in the main thread when stopping the thread. }
///<summary>Starts the eService monitoring thread that will run once a minute. Only runs if the user currently logged in has the eServices permission.</summary> private void StartEServiceMonitoring() { //If the user currently logged in has permission to view eService settings, turn on the listener monitor. if(Security.CurUser==null || !Security.IsAuthorized(Permissions.EServicesSetup,true)) { return;//Do not start the listener service monitor for users without permission. } if(_odThreadEServices==null) { //Create a separate thread that will run every 60 seconds to monitor eService signals. _odThreadEServices=new ODThread(60000,ProcessEServiceSignals); //Add exception handling just in case MySQL is unreachable at any point in the lifetime of this session. _odThreadEServices.AddExceptionHandler(EServiceMonitoringException); _odThreadEServices.Name="eService Monitoring Thread"; _odThreadEServices.GroupName="eServiceThreads"; } _odThreadEServices.Start(); }
///<summary>What the thread does right before it dies.</summary> public virtual void OnThreadExit(ODThread odThread) { }
///<summary>What the thread does every time the specified interval of time has passed.</summary> public abstract void OnThreadRun(ODThread odThread);
///<summary>Spread the given actions over the given numThreads. Blocks until threads have completed or timeout is reached. ///If numThreads is not provided then numThreads will default to Environment.ProcessorCount. This is typically what you should let happen. ///If onException is provided then one and only one onException event will be raised when any number of exceptions occur. ///All actions will run to completion regardless if any/all throw unhandled exceptions. ///If the timeout is reached, all threads will be killed and their corresponding actions will not complete. This can leave data in an ///undefined state, for example, if an action times out before instantiating an object, the object will be null. ///Throws exception on main thread if any action throws and unhandled exception and no onException was provided.</summary> public static void RunParallel(List<Action> listActions,int timeoutMS=Timeout.Infinite,int numThreads=0,ExceptionDelegate onException=null, bool doRunOnCurrentThreadIf1Processor=false) { //Use as many threads as required by default. int threadCount=numThreads; if(threadCount<=0) { //No requirement on thread count was given so use the number of processor cores. threadCount=Environment.ProcessorCount; //Using at least 8 has neglibile negative impact so if the user didn't otherwise specify, use at least 8 threads. if(threadCount<8) { threadCount=8; } } Exception exceptionFirst=null; void HandleException() { if(exceptionFirst!=null) { //One of the actions threw an unhandled exception. if(onException==null) { //No handler was provided so throw. ExceptionDispatchInfo exInfo=ExceptionDispatchInfo.Capture(exceptionFirst); exInfo.Throw(); } //Caller wants to know about this exception so tell them. onException(exceptionFirst); } } if(threadCount==1 && doRunOnCurrentThreadIf1Processor) { foreach(Action action in listActions) { try { action(); } catch(Exception ex) { exceptionFirst=exceptionFirst??ex;//First exception in will get thrown. } } HandleException(); return; } //Make a group of threads to spread out the workload. List<Action> listActionsCur=new List<Action>(); int actionsPerThread=(int)Math.Ceiling((double)listActions.Count/threadCount); object locker=new object(); //No one outside of this method cares about this group name. They have no authority over this group. int threadID=1; string threadGroupGUID=Guid.NewGuid().ToString(); string threadGroupName="ODThread.ThreadPool()"+threadGroupGUID; for(int i = 0;i<listActions.Count;i++) { //Add to the current thread pool. listActionsCur.Add(listActions[i]); //If this thread pool is full then start it. if(listActionsCur.Count==actionsPerThread||i==(listActions.Count-1)) { ODThread odThread=new ODThread(new WorkerDelegate((ODThread o) => { ((List<Action>)o.Tag).ForEach(x => x()); })); odThread.Tag=new List<Action>(listActionsCur); odThread.Name=threadGroupName+"-"+threadID; odThread.GroupName=threadGroupName; odThread.AddExceptionHandler(new ExceptionDelegate((Exception e) => { lock (locker) { if(exceptionFirst==null) { //First in wins. exceptionFirst=e; } } })); //We just started a new thread pool so start a new one. listActionsCur.Clear(); odThread.Start(true); threadID++; } } //Wait for all appointment drawing threads to finish. JoinThreadsByGroupName(timeoutMS,threadGroupName,true); //We are back on the thread that called us now. HandleException(); }
///<summary>Spread the given actions over the given numThreads. Blocks until threads have completed or timeout is reached. ///If numThreads is not provided then numThreads will default to Environment.ProcessorCount. This is typically what you should let happen. ///If onException is provided then one and only one onException event will be raised when any number of exceptions occur. ///All actions will run to completion regardless if any/all throw unhandled exceptions. ///Throws exception on main thread if any action throws and unhandled exception and no onException was provided.</summary> public static void RunParallel(List <Action> listActions, TimeSpan timeout, int numThreads = 0, ExceptionDelegate onException = null) { //Use as many threads as required by default. int threadCount = numThreads; if (threadCount <= 0) { //No requirement on thread count was given so use the number of processor cores. threadCount = Environment.ProcessorCount; //Using at least 8 has neglibile negative impact so if the user didn't otherwise specify, use at least 8 threads. if (threadCount < 8) { threadCount = 8; } } //Make a group of threads to spread out the workload. List <Action> listActionsCur = new List <Action>(); Exception exceptionFirst = null; int actionsPerThread = (int)Math.Ceiling((double)listActions.Count / threadCount); object locker = new object(); //No one outside of this method cares about this group name. They have no authority over this group. string threadGroupName = "ODThread.ThreadPool()" + Guid.NewGuid().ToString(); for (int i = 0; i < listActions.Count; i++) { //Add to the current thread pool. listActionsCur.Add(listActions[i]); //If this thread pool is full then start it. if (listActionsCur.Count == actionsPerThread || i == (listActions.Count - 1)) { ODThread odThread = new ODThread(new WorkerDelegate((ODThread o) => { ((List <Action>)o.Tag).ForEach(x => x()); })); odThread.Tag = new List <Action>(listActionsCur); odThread.GroupName = threadGroupName; odThread.AddExceptionHandler(new ExceptionDelegate((Exception e) => { lock (locker) { if (exceptionFirst == null) //First in wins. { exceptionFirst = e; } } })); //We just started a new thread pool so start a new one. listActionsCur.Clear(); odThread.Start(true); } } //Wait for all appointment drawing threads to finish. JoinThreadsByGroupName((int)timeout.TotalMilliseconds, threadGroupName, true); //We are back on the thread that called us now. if (exceptionFirst != null) //One of the actions threw an unhandled exception. { if (onException == null) //No handler was provided so throw. { throw exceptionFirst; } //Caller wants to know about this exception so tell them. onException(exceptionFirst); } }