private void FormProgressStatus_Shown(object sender, EventArgs e) { //Spawn a separate thread that will monitor if this progress form should has indicated that it needs to close. //This thread will be a fail-safe in the sense that it will constantly monitor a separate indicator that this window should close. ODThread threadForceCloseMonitor = new ODThread(100, new ODThread.WorkerDelegate((o) => { if (_hasClosed) { o.QuitAsync(); //Stop monitoring as soon as we detect that this window has been "closed". return; } if (ForceClose) { //Something triggered the fact that this form should have closed. this.InvokeIfRequired(() => { DialogResult = DialogResult.OK; ODException.SwallowAnyException(() => Close()); //Can throw exceptions for many reasons. }); o.QuitAsync(); //We tried our best to "unstuck" this window. The user will have to Alt + F4 this window closed? return; } })); threadForceCloseMonitor.AddExceptionHandler((ex) => { ex.DoNothing(); //The form might stay open forever which was already happening... we tried our best. }); threadForceCloseMonitor.Name = "FormProgressStatusMonitor_" + DateTime.Now.Ticks; threadForceCloseMonitor.Start(); }
///<summary>Non-blocking call. Every type of progress window should eventually call this method which does the hard stuff for the calling method. ///Spawns a separate thread that will instantiate a FormProgressBase within the new thread's context by invoking the func passed in. ///The FormProgressBase that funcGetNewProgress returns should be a form that is instatiated within the func (in order to avoid cross-threading). ///The global static FormProgressCurS will get set to the newly instantiated progress so that the entire application knows progress is showing. ///Finally returning a close action for the calling method to invoke whenever long computations are finished. ///Two critical portions of the closing method are 1 - it closes progress gracefully and 2 - FormProgressCurS gets set to null.</summary> public static Action ShowProgressBase(Func <FormProgressBase> funcGetNewProgress, string threadName = "Thread_ODProgress_ShowProgressBase") { if (ODEnvironment.IsWindows7(false) || ODInitialize.IsRunningInUnitTest) { return(new Action(() => { //Do nothing. })); } FormProgressBase FormPB = null; ManualResetEvent manualReset = new ManualResetEvent(false); ODThread odThread = new ODThread(o => { //It is very important that this particular thread instantiates the form and not the calling method. //This is what allows the progress window to show and be interacted with without joining or invoking back to the parent thread. FormPB = funcGetNewProgress(); AddActiveProgressWindow(FormPB); //Let the entire application know that a progress window is showing. FormPB.Shown += (obj, eArg) => manualReset.Set(); FormPB.FormClosed += (obj, eArg) => RemoveActiveProgressWindow(FormPB); FormPB.ShowDialog(); //We cannot utilize the "owner" overload because that would cause a cross threaded exception. }); odThread.SetApartmentState(ApartmentState.STA); //This is required for ReportComplex due to the history UI elements. odThread.AddExceptionHandler(e => e.DoNothing()); //The progress window had an exception... Not worth crashing the program over this. odThread.Name = threadName; odThread.Start(); //Force the calling thread to wait for the progress window to actually show to the user before continuing. manualReset.WaitOne(); return(() => { FormPB.ForceClose = true; odThread.Join(Timeout.Infinite); }); }
///<summary>Sends a requests to ODCloudClient and waits for a response. Throws any exception that ODCloudClient returns.</summary> private static string SendToODCloudClientSynchronously(ODCloudClientData cloudClientData, CloudClientAction cloudClientAction, int timeoutSecs = 30) { bool hasReceivedResponse = false; string odCloudClientResponse = ""; Exception exFromThread = null; void onReceivedResponse(string response) { hasReceivedResponse = true; odCloudClientResponse = response; } ODThread thread = new ODThread(o => { SendToODCloudClient(cloudClientData, cloudClientAction, onReceivedResponse); }); thread.Name = "SendToODCloudClient"; thread.AddExceptionHandler(e => exFromThread = e); thread.Start(); DateTime start = DateTime.Now; ODProgress.ShowAction(() => { while (!hasReceivedResponse && exFromThread == null && (DateTime.Now - start).TotalSeconds < timeoutSecs) { Thread.Sleep(100); } }); if (exFromThread != null) { throw exFromThread; } if (!hasReceivedResponse) { throw new ODException("Unable to communicate with OD Cloud Client.", ODException.ErrorCodes.ODCloudClientTimeout); } CloudClientResult result = JsonConvert.DeserializeObject <CloudClientResult>(odCloudClientResponse); if (result.ResultCodeEnum == CloudClientResultCode.ODException) { ODException odEx = JsonConvert.DeserializeObject <ODException>(result.ResultData); throw odEx; } if (result.ResultCodeEnum == CloudClientResultCode.Error) { throw new Exception(result.ResultData); } return(result.ResultData); }
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>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); } }