using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Project.StateMachine { public partial class StateMachine : IDisposable { public DateTime UpdateTime; public TimeSpan RunSpeed = new TimeSpan(0); private Boolean bLoop = true; private DateTime StepStartTime; private byte _messageOption; private System.Threading.Thread worker; private Boolean firstRun = false; public Boolean WaitFirstRun = false; public StateMachine() { WaitFirstRun = false; UpdateTime = DateTime.Now; _messageOption = 0xFF; //모든메세지가 오도록 한다. worker = new System.Threading.Thread(new System.Threading.ThreadStart(Loop)) { IsBackground = false }; StepStartTime = DateTime.Parse("1982-11-23"); } ~StateMachine() { Dispose(false); } #region "Dispose" private bool disposed = false; public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { // Check to see if Dispose has already been called. if (!this.disposed) { if (disposing) { // Dispose managed resources. } Stop(); // unmanaged resources here. // If disposing is false, // only the following code is executed. disposed = true; } } #endregion public void Stop() { if(bLoop != false) bLoop = false; //if (worker.IsAlive) // if (worker.Join(1000) == false) // worker.Abort(); } public void Start() { if (worker.ThreadState == System.Threading.ThreadState.Stopped) { worker = new System.Threading.Thread(new System.Threading.ThreadStart(Loop)) { IsBackground = false }; } bLoop = true; worker.Start(); } private Boolean _isthreadrun; public Boolean IsThreadRun { get { return _isthreadrun; } } public int LoopCount { get { return _loopCount; } } public DateTime LastLoopTime { get { return _lastLoopTime; } } private int _loopCount = 0; private DateTime _lastLoopTime = DateTime.Now; void Loop() { _isthreadrun = true; if (GetMsgOpt(EMsgOpt.NORMAL)) RaiseMessage("SM", "Start"); try { while (bLoop) { _loopCount++; _lastLoopTime = DateTime.Now; // 루프 동작 확인용 로그 (10초마다) if (_loopCount % 10000 == 0) { RaiseMessage("SM-LOOP", $"Loop alive - Count:{_loopCount}, bLoop:{bLoop}, Step:{Step}"); } RunSpeed = DateTime.Now - UpdateTime; //이전업데이트에서 현재 시간의 오차 한바퀴 시간이 표시됨 UpdateTime = DateTime.Now; //항상 작동하는 경우 try { var spsHandler = SPS; if (spsHandler != null) { var spsStartTime = DateTime.Now; var spsTask = System.Threading.Tasks.Task.Run(() => { try { spsHandler(this, new EventArgs()); } catch (Exception ex) { RaiseMessage("SM-ERROR-SPS", $"SPS Exception: {ex.Message}\n{ex.StackTrace}"); } }); // 타임아웃 1초 if (!spsTask.Wait(1000)) { var elapsed = (DateTime.Now - spsStartTime).TotalSeconds; RaiseMessage("SM-TIMEOUT", $"SPS 이벤트 타임아웃 ({elapsed:F2}초 초과) - Step:{Step}"); } } } catch (Exception ex) { RaiseMessage("SM-ERROR-SPS", $"SPS Exception: {ex.Message}\n{ex.StackTrace}"); } //작동스텝이 변경되었다면 그것을 알림 처리한다. if (GetNewStep != Step) { if (GetMsgOpt(EMsgOpt.STEPCHANGE)) RaiseMessage("SM-STEP", string.Format("Step Changed {0} >> {1}", Step, GetNewStep)); StepApply(); firstRun = true; StepStartTime = DateTime.Now; } else { //팝업메세지의 종료를 기다리는 식에서 문제가 생긴다 firstRun = WaitFirstRun; } //동작중에 발생하는 이벤트 var runningHandler = Running; if (runningHandler != null) { try { var runningStartTime = DateTime.Now; var runningTask = System.Threading.Tasks.Task.Run(() => { try { runningHandler(this, new RunningEventArgs(Step, firstRun, StepRunTime)); } catch (Exception ex) { RaiseMessage("SM-ERROR-RUNNING", $"Running Exception: {ex.Message}\n{ex.StackTrace}"); } }); // 타임아웃 2초 if (!runningTask.Wait(2000)) { var elapsed = (DateTime.Now - runningStartTime).TotalSeconds; RaiseMessage("SM-TIMEOUT", $"Running 이벤트 타임아웃 ({elapsed:F2}초 초과) - Step:{Step}, FirstRun:{firstRun}"); } } catch (Exception ex) { RaiseMessage("SM-ERROR-RUNNING", $"Running Exception: {ex.Message}\n{ex.StackTrace}"); } } System.Threading.Thread.Sleep(1); } } catch (Exception ex) { RaiseMessage("SM-FATAL", $"Loop Fatal Exception: {ex.Message}\n{ex.StackTrace}"); } finally { _isthreadrun = false; if (GetMsgOpt(EMsgOpt.NORMAL)) RaiseMessage("SM", $"Stop - LoopCount:{_loopCount}"); } } /// /// 상태머신의 작동상태 /// public eSMStep Step { get { return _step; } } /// /// 상태머신값을 숫자로 반환 합니다. (20이하는 시스템상태이고 그 이상은 사용자 상태) /// public byte StepValue { get { return (byte)this._step; } } /// /// 현재 진행 스텝을 강제로 변경합니다. /// 일반적인 상태변화는 setNewStep 을 사용하세요. /// /// public void SetCurrentStep(ERunStep step) { RaiseMessage("SM-STEP", string.Format("강제 스텝 변경 {0}->{1}", _runstepo, step)); _runstepo = step; _runstepn = step; } public void SetNewStep(eSMStep newstep_, bool force = false) { if (Step != newstep_) { if(force) { RaiseMessage("SM-STEP", string.Format("강제스텝전환 {0} -> {1}", Step, _newstep)); OldStep = _step; _step = newstep_; _newstep = newstep_; // 비동기로 이벤트 발생 (블로킹 방지) var handler = StepChanged; if (handler != null) { var args = new StepChangeEventArgs(OldStep, newstep_); System.Threading.ThreadPool.QueueUserWorkItem(_ => { try { handler(this, args); } catch { /* 이벤트 핸들러 예외 무시 */ } }); } } else { if (_newstep != newstep_) { //바뀌도록 예약을 한다. _newstep = newstep_; if (GetMsgOpt(EMsgOpt.STEPCHANGE)) RaiseMessage("SM-STEP", string.Format("Step Reserve {0} -> {1}", Step, _newstep)); } else { //예약은 되어있는데 아직 바뀐것은 아니다. if (GetMsgOpt(EMsgOpt.STEPCHANGE)) RaiseMessage("SM-STEP", string.Format("Step Already Reserve {0} -> {1}", Step, _newstep)); } } } } #region "runstep" private int _runStepSeq = 0; public int RunStepSeq { get { return _runStepSeq; } } private DateTime runStepStartTime = DateTime.Parse("1982-11-23"); private ERunStep _runstepn = ERunStep.READY; private ERunStep _runstepo = ERunStep.READY; public ERunStep RunStep { get { return _runstepo; } } public ERunStep RunStepNew { get { return _runstepn; } } public void SetNewRunStep(ERunStep newStep) { if (_runstepn != newStep) { // Pub.log.Add(string.Format("set new run step {0}->{1}", _runstepn, newStep)); _runstepn = newStep; } } public void ApplyRunStep() { if (_runstepn != _runstepo) { //Pub.log.Add(string.Format("apply new run step {0}->{1}", _runstepo, _runstepn)); _runStepSeq = 0; _runstepo = _runstepn; UpdateRunStepSeq();// runStepStartTime = DateTime.Now; } } public TimeSpan GetRunSteptime { get { return DateTime.Now - runStepStartTime; } } public void SetStepSeq(byte value) { _runStepSeq = value; if (GetMsgOpt(EMsgOpt.NORMAL)) { if (_runStepSeq != value) //변화가잇는겨웅에만 처리 220628 RaiseMessage("STEPSEQ", string.Format("Step Sequence Jump {0} to {1}", _runStepSeq, value)); } } public void UpdateRunStepStartTime() { runStepStartTime = DateTime.Now; // Pub.log.Add("Update RunStep Start Time"); } public void UpdateRunStepSeq(int incvalue = 1) { _runStepSeq += incvalue; UpdateRunStepStartTime(); // Pub.log.Add(string.Format("스텝({0}) 시퀀스증가 신규값={1}", runStep, _runStepSeq)); } public void ResetRunStepSeq() { _runStepSeq = 1; UpdateRunStepStartTime(); } /// /// runstep 시퀀스값을 1로 설정하고 시작시간도 업데이트 합니다 /// 기본 스텝상태를 READY로 변경 합니다 /// public void ClearRunStep() { _runStepSeq = 1; runStepStartTime = DateTime.Now; _runstepn = ERunStep.READY; _runstepo = ERunStep.READY; } #endregion public eSMStep GetNewStep { get { return _newstep; } } private eSMStep _newstep = eSMStep.NOTSET; public eSMStep OldStep = eSMStep.NOTSET; //171214 private eSMStep _step; /// /// newstep 의 값을 step 에 적용합니다. /// private void StepApply() { var ostep = _step; OldStep = _step; _step = _newstep; // 비동기로 이벤트 발생 (블로킹 방지) var handler = StepChanged; if (handler != null) { var args = new StepChangeEventArgs(ostep, _step); System.Threading.ThreadPool.QueueUserWorkItem(_ => { try { handler(this, args); } catch { /* 이벤트 핸들러 예외 무시 */ } }); } } //171214 /// /// 메세지 출력옵션을 변경 합니다. /// /// /// public void SetMsgOpt(EMsgOpt opt, Boolean value) { byte pos = (byte)opt; if (value) _messageOption = (byte)(_messageOption | (1 << pos)); else _messageOption = (byte)(_messageOption & ~(1 << pos)); } public void SetMegOptOn() { _messageOption = 0xFF; } public void SetMsgOptOff() { _messageOption = 0; } public Boolean GetMsgOpt(EMsgOpt opt) { byte pos = (byte)opt; return (_messageOption & (1 << pos)) > 0; } public TimeSpan StepRunTime { get { if (IsThreadRun == false || bLoop == false || StepStartTime.Year == 1982) return new TimeSpan(0); else return DateTime.Now - StepStartTime; } } public Boolean bPause = false; /// /// 상태머신이 실제 동작중인지 확인합니다. /// 검사상태 : Step = Run, IsThreadRun, bPause = false /// public Boolean IsRun { get { if (Step == eSMStep.RUN && _isthreadrun && bPause == false) return true; else return false; } } } }