Fix: 상태머신 루프 블로킹 문제 수정 - SPS 이벤트 핸들러 비동기 처리 및 타임아웃 보호 추가
- sm_SPS 이벤트 핸들러에서 장치 연결 및 상태 전송을 비동기로 처리 - DeviceConnectionWorker 스레드로 장치 연결 분리 - SPS(1초), Running(2초) 타임아웃 보호 추가 - 상태머신 모니터링 디버그 창 추가 (fStateMachineDebug) - F11/F12 단축키로 스레드 덤프 및 디버그 브레이크 지원 - RaiseMessage 이벤트 비동기 처리로 로그 블로킹 방지
This commit is contained in:
@@ -193,6 +193,12 @@
|
||||
<Compile Include="Dialog\fCounter.Designer.cs">
|
||||
<DependentUpon>fCounter.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Dialog\fStateMachineDebug.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Dialog\fStateMachineDebug.Designer.cs">
|
||||
<DependentUpon>fStateMachineDebug.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Dialog\fUpdateForm.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
|
||||
46
Cs_HMI/Project/Dialog/fStateMachineDebug.Designer.cs
generated
Normal file
46
Cs_HMI/Project/Dialog/fStateMachineDebug.Designer.cs
generated
Normal file
@@ -0,0 +1,46 @@
|
||||
namespace Project.Dialog
|
||||
{
|
||||
partial class fStateMachineDebug
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// fStateMachineDebug
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 12F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.ClientSize = new System.Drawing.Size(800, 600);
|
||||
this.Name = "fStateMachineDebug";
|
||||
this.Text = "상태머신 디버그";
|
||||
this.ResumeLayout(false);
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
237
Cs_HMI/Project/Dialog/fStateMachineDebug.cs
Normal file
237
Cs_HMI/Project/Dialog/fStateMachineDebug.cs
Normal file
@@ -0,0 +1,237 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
using System.Threading;
|
||||
using System.Diagnostics;
|
||||
using AR;
|
||||
using COMM;
|
||||
|
||||
namespace Project.Dialog
|
||||
{
|
||||
public partial class fStateMachineDebug : Form
|
||||
{
|
||||
private System.Windows.Forms.Timer updateTimer;
|
||||
private TextBox txtDebugInfo;
|
||||
private Button btnRefresh;
|
||||
private Button btnForceRestart;
|
||||
|
||||
public fStateMachineDebug()
|
||||
{
|
||||
InitializeComponent();
|
||||
InitializeCustomComponents();
|
||||
|
||||
updateTimer = new System.Windows.Forms.Timer();
|
||||
updateTimer.Interval = 500; // 0.5초마다 업데이트
|
||||
updateTimer.Tick += UpdateTimer_Tick;
|
||||
updateTimer.Start();
|
||||
}
|
||||
|
||||
private void InitializeCustomComponents()
|
||||
{
|
||||
this.Text = "상태머신 디버그 모니터";
|
||||
this.Size = new Size(800, 600);
|
||||
this.StartPosition = FormStartPosition.CenterScreen;
|
||||
this.FormBorderStyle = FormBorderStyle.Sizable;
|
||||
|
||||
// TextBox 생성
|
||||
txtDebugInfo = new TextBox
|
||||
{
|
||||
Multiline = true,
|
||||
ScrollBars = ScrollBars.Both,
|
||||
Dock = DockStyle.Fill,
|
||||
Font = new Font("Consolas", 9),
|
||||
ReadOnly = true,
|
||||
BackColor = Color.Black,
|
||||
ForeColor = Color.LimeGreen
|
||||
};
|
||||
|
||||
// 버튼 패널
|
||||
var buttonPanel = new Panel
|
||||
{
|
||||
Dock = DockStyle.Bottom,
|
||||
Height = 50
|
||||
};
|
||||
|
||||
btnRefresh = new Button
|
||||
{
|
||||
Text = "새로고침",
|
||||
Location = new Point(10, 10),
|
||||
Size = new Size(100, 30)
|
||||
};
|
||||
btnRefresh.Click += (s, e) => UpdateDebugInfo();
|
||||
|
||||
btnForceRestart = new Button
|
||||
{
|
||||
Text = "상태머신 재시작",
|
||||
Location = new Point(120, 10),
|
||||
Size = new Size(120, 30),
|
||||
BackColor = Color.IndianRed
|
||||
};
|
||||
btnForceRestart.Click += BtnForceRestart_Click;
|
||||
|
||||
var btnBreakNow = new Button
|
||||
{
|
||||
Text = "즉시 중단",
|
||||
Location = new Point(250, 10),
|
||||
Size = new Size(100, 30),
|
||||
BackColor = Color.Orange
|
||||
};
|
||||
btnBreakNow.Click += (s, ev) => {
|
||||
if (System.Diagnostics.Debugger.IsAttached)
|
||||
{
|
||||
System.Diagnostics.Debugger.Break();
|
||||
}
|
||||
else
|
||||
{
|
||||
MessageBox.Show("디버거가 연결되지 않았습니다.", "알림");
|
||||
}
|
||||
};
|
||||
|
||||
buttonPanel.Controls.Add(btnRefresh);
|
||||
buttonPanel.Controls.Add(btnForceRestart);
|
||||
buttonPanel.Controls.Add(btnBreakNow);
|
||||
|
||||
this.Controls.Add(txtDebugInfo);
|
||||
this.Controls.Add(buttonPanel);
|
||||
}
|
||||
|
||||
private void UpdateTimer_Tick(object sender, EventArgs e)
|
||||
{
|
||||
UpdateDebugInfo();
|
||||
}
|
||||
|
||||
private void UpdateDebugInfo()
|
||||
{
|
||||
try
|
||||
{
|
||||
var info = new System.Text.StringBuilder();
|
||||
info.AppendLine("=== 상태머신 디버그 정보 ===");
|
||||
info.AppendLine($"현재 시간: {DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}");
|
||||
info.AppendLine();
|
||||
|
||||
if (PUB.sm != null)
|
||||
{
|
||||
info.AppendLine($"상태머신 객체: 존재");
|
||||
info.AppendLine($"IsThreadRun: {PUB.sm.IsThreadRun}");
|
||||
info.AppendLine($"LoopCount: {PUB.sm.LoopCount}");
|
||||
|
||||
var lastLoopElapsed = (DateTime.Now - PUB.sm.LastLoopTime).TotalSeconds;
|
||||
var loopStatus = lastLoopElapsed < 1 ? "정상" : "경고!";
|
||||
var loopColor = lastLoopElapsed < 1 ? "" : " <<<<<<";
|
||||
info.AppendLine($"마지막 루프: {lastLoopElapsed:F2}초 전 [{loopStatus}]{loopColor}");
|
||||
|
||||
info.AppendLine($"현재 Step: {PUB.sm.Step}");
|
||||
info.AppendLine($"현재 RunStep: {PUB.sm.RunStep}");
|
||||
info.AppendLine($"Pause 상태: {PUB.sm.bPause}");
|
||||
info.AppendLine($"WaitFirstRun: {PUB.sm.WaitFirstRun}");
|
||||
|
||||
// 스레드 정보
|
||||
var smThread = GetStateMachineThread();
|
||||
if (smThread != null)
|
||||
{
|
||||
info.AppendLine();
|
||||
info.AppendLine($"스레드 이름: {smThread.Name ?? "N/A"}");
|
||||
info.AppendLine($"스레드 ID: {smThread.ManagedThreadId}");
|
||||
info.AppendLine($"스레드 상태: {smThread.ThreadState}");
|
||||
info.AppendLine($"IsAlive: {smThread.IsAlive}");
|
||||
info.AppendLine($"IsBackground: {smThread.IsBackground}");
|
||||
}
|
||||
else
|
||||
{
|
||||
info.AppendLine();
|
||||
info.AppendLine("경고: 상태머신 스레드를 찾을 수 없음!");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
info.AppendLine("오류: 상태머신 객체가 NULL입니다!");
|
||||
}
|
||||
|
||||
info.AppendLine();
|
||||
info.AppendLine("=== 변수 상태 ===");
|
||||
info.AppendLine($"FLAG_AUTORUN: {VAR.BOOL[eVarBool.FLAG_AUTORUN]}");
|
||||
info.AppendLine($"EMERGENCY: {VAR.BOOL[eVarBool.EMERGENCY]}");
|
||||
info.AppendLine($"FLAG_SYNC: {VAR.BOOL[eVarBool.FLAG_SYNC]}");
|
||||
|
||||
info.AppendLine();
|
||||
info.AppendLine("=== 하드웨어 연결 상태 ===");
|
||||
info.AppendLine($"AGV: {(PUB.AGV?.IsOpen ?? false)} - {PUB.setting.Port_AGV}");
|
||||
info.AppendLine($"XBE: {(PUB.XBE?.IsOpen ?? false)} - {PUB.setting.Port_XBE}");
|
||||
info.AppendLine($"BMS: {(PUB.BMS?.IsOpen ?? false)} - {PUB.setting.Port_BAT}");
|
||||
|
||||
info.AppendLine();
|
||||
info.AppendLine("=== 관리되는 스레드 목록 ===");
|
||||
var currentProcess = Process.GetCurrentProcess();
|
||||
info.AppendLine($"총 프로세스 스레드 수: {currentProcess.Threads.Count}");
|
||||
|
||||
// 모든 관리되는 스레드 정보 출력
|
||||
foreach (ProcessThread thread in currentProcess.Threads)
|
||||
{
|
||||
var state = thread.ThreadState;
|
||||
var waitReason = thread.ThreadState == System.Diagnostics.ThreadState.Wait ?
|
||||
$", WaitReason: {thread.WaitReason}" : "";
|
||||
info.AppendLine($" Thread {thread.Id}: State={state}, Priority={thread.PriorityLevel}{waitReason}");
|
||||
}
|
||||
|
||||
txtDebugInfo.Text = info.ToString();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
txtDebugInfo.Text = $"오류 발생: {ex.Message}\r\n{ex.StackTrace}";
|
||||
}
|
||||
}
|
||||
|
||||
private Thread GetStateMachineThread()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Reflection을 사용하여 StateMachine의 private worker 필드 접근
|
||||
var smType = PUB.sm.GetType();
|
||||
var threadField = smType.GetField("worker",
|
||||
System.Reflection.BindingFlags.NonPublic |
|
||||
System.Reflection.BindingFlags.Instance);
|
||||
|
||||
if (threadField != null)
|
||||
{
|
||||
return threadField.GetValue(PUB.sm) as Thread;
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
return null;
|
||||
}
|
||||
|
||||
private void BtnForceRestart_Click(object sender, EventArgs e)
|
||||
{
|
||||
var result = MessageBox.Show(
|
||||
"상태머신을 강제로 재시작하시겠습니까?\n이 작업은 위험할 수 있습니다.",
|
||||
"경고",
|
||||
MessageBoxButtons.YesNo,
|
||||
MessageBoxIcon.Warning);
|
||||
|
||||
if (result == DialogResult.Yes)
|
||||
{
|
||||
try
|
||||
{
|
||||
PUB.log.Add("StateMachine", "사용자가 강제 재시작 요청");
|
||||
PUB.sm.Stop();
|
||||
System.Threading.Thread.Sleep(1000);
|
||||
PUB.sm.Start();
|
||||
PUB.log.Add("StateMachine", "강제 재시작 완료");
|
||||
MessageBox.Show("상태머신이 재시작되었습니다.", "완료");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"재시작 실패: {ex.Message}", "오류");
|
||||
PUB.log.AddE($"상태머신 재시작 실패: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnFormClosing(FormClosingEventArgs e)
|
||||
{
|
||||
updateTimer?.Stop();
|
||||
updateTimer?.Dispose();
|
||||
base.OnFormClosing(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,12 +38,12 @@ namespace Project
|
||||
dev.BaudRate = baud;
|
||||
if (dev.Open())
|
||||
{
|
||||
PUB.log.Add(port, "연결완료");
|
||||
PUB.log.Add(port, $"[AGV:{port}:{baud}]연결완료");
|
||||
}
|
||||
else
|
||||
{
|
||||
var errmessage = dev.errorMessage;
|
||||
PUB.log.Add("ERROR-" + port, errmessage);
|
||||
PUB.log.AddE($"[AGV:{port}:{baud}] {errmessage}");
|
||||
}
|
||||
VAR.TIME.Update(conn);
|
||||
VAR.TIME.Update(conntry);
|
||||
@@ -76,12 +76,12 @@ namespace Project
|
||||
dev.BaudRate = baud;
|
||||
if (dev.Open())
|
||||
{
|
||||
PUB.log.Add(port, "연결완료");
|
||||
PUB.log.Add(port, $"[XBEE:{port}:{baud}]연결완료");
|
||||
}
|
||||
else
|
||||
{
|
||||
var errmessage = dev.errorMessage;
|
||||
PUB.log.Add("ERROR-" + port, errmessage);
|
||||
PUB.log.AddE($"[XBEE:{port}:{baud}] {errmessage}");
|
||||
}
|
||||
VAR.TIME.Update(conn);
|
||||
VAR.TIME.Update(conntry);
|
||||
@@ -135,6 +135,7 @@ namespace Project
|
||||
// 장치 연결 처리 워커 (별도 쓰레드에서 실행)
|
||||
private void DeviceConnectionWorker()
|
||||
{
|
||||
PUB.log.Add($"Start DeviceConnectionWorker");
|
||||
while (isDeviceConnectionRunning)
|
||||
{
|
||||
try
|
||||
@@ -155,9 +156,10 @@ namespace Project
|
||||
var ts = VAR.TIME.RUN(eVarTime.LastConn_BAT);
|
||||
if (ts.TotalSeconds > 3)
|
||||
{
|
||||
Console.WriteLine($"bms connect to {PUB.setting.Port_BAT}");
|
||||
PUB.log.Add($"bms connect to {PUB.setting.Port_BAT}");
|
||||
PUB.BMS.PortName = PUB.setting.Port_BAT;
|
||||
PUB.BMS.Open();
|
||||
if (PUB.BMS.Open())
|
||||
PUB.log.AddI($"BMS Connected({PUB.setting.Port_BAT})");
|
||||
|
||||
VAR.TIME.Update(eVarTime.LastConn_BAT);
|
||||
VAR.TIME.Update(eVarTime.LastConnTry_BAT);
|
||||
@@ -166,7 +168,7 @@ namespace Project
|
||||
else if (PUB.BMS.IsValid == false)
|
||||
{
|
||||
var ts = VAR.TIME.RUN(eVarTime.LastConnTry_BAT);
|
||||
if (ts.TotalSeconds > 10)
|
||||
if (ts.TotalSeconds > (PUB.setting.interval_bms * 2.5))
|
||||
{
|
||||
Console.WriteLine("bms auto disconnect");
|
||||
PUB.BMS.Close();
|
||||
@@ -188,35 +190,61 @@ namespace Project
|
||||
void sm_SPS(object sender, EventArgs e)
|
||||
{
|
||||
if (PUB.sm.Step < eSMStep.IDLE || PUB.sm.Step >= eSMStep.CLOSING) return;
|
||||
//return;
|
||||
|
||||
// 장치 연결 쓰레드가 실행 중이 아니면 시작
|
||||
if (!isDeviceConnectionRunning)
|
||||
try
|
||||
{
|
||||
StartDeviceConnectionThread();
|
||||
}
|
||||
|
||||
//지그비상태전송
|
||||
if (PUB.XBE != null && PUB.XBE.IsOpen)
|
||||
{
|
||||
//일정간격으로 상태를 전송한다
|
||||
if (PUB.XBE.LastStatusSendTime.Year == 1982) PUB.XBE.LastStatusSendTime = DateTime.Now.AddSeconds(1);
|
||||
var ts = DateTime.Now - PUB.XBE.LastStatusSendTime;
|
||||
if (ts.TotalSeconds >= PUB.setting.interval_xbe)
|
||||
// 장치 연결 쓰레드가 실행 중이 아니면 시작 (한 번만)
|
||||
if (!isDeviceConnectionRunning)
|
||||
{
|
||||
PUB.XBE.SendStatus();
|
||||
StartDeviceConnectionThread();
|
||||
}
|
||||
|
||||
//지그비상태전송 (비동기로 실행)
|
||||
if (PUB.XBE != null && PUB.XBE.IsOpen)
|
||||
{
|
||||
//일정간격으로 상태를 전송한다
|
||||
if (PUB.XBE.LastStatusSendTime.Year == 1982) PUB.XBE.LastStatusSendTime = DateTime.Now.AddSeconds(1);
|
||||
var ts = DateTime.Now - PUB.XBE.LastStatusSendTime;
|
||||
if (ts.TotalSeconds >= PUB.setting.interval_xbe)
|
||||
{
|
||||
|
||||
System.Threading.ThreadPool.QueueUserWorkItem(_ =>
|
||||
{
|
||||
try { PUB.XBE.SendStatus(); }
|
||||
catch { /* 무시 */ }
|
||||
finally { PUB.XBE.LastStatusSendTime = DateTime.Now; }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//배터리쿼리 (비동기로 실행)
|
||||
if (PUB.BMS != null && PUB.BMS.IsOpen)
|
||||
{
|
||||
if (PUB.BMS.lastSendTime.Year == 1982) PUB.BMS.lastSendTime = DateTime.Now.AddSeconds(1);
|
||||
var ts = DateTime.Now - PUB.BMS.lastSendTime;
|
||||
if (ts.TotalSeconds >= PUB.setting.interval_bms)
|
||||
{
|
||||
//PUB.BMS.lastSendTime = DateTime.Now;
|
||||
//System.Threading.ThreadPool.QueueUserWorkItem(_ =>
|
||||
//{
|
||||
// try { PUB.BMS.SendQuery(); }
|
||||
// catch { /* 무시 */ }
|
||||
// finally { PUB.BMS.lastSendTime = DateTime.Now; }
|
||||
//});
|
||||
}
|
||||
|
||||
// Update_BatteryWarnSpeak도 비동기로
|
||||
//System.Threading.ThreadPool.QueueUserWorkItem(_ =>
|
||||
//{
|
||||
try { Update_BatteryWarnSpeak(); }
|
||||
catch { /* 무시 */ }
|
||||
//});
|
||||
}
|
||||
}
|
||||
|
||||
//배터리쿼리
|
||||
if (PUB.BMS != null && PUB.BMS.IsOpen)
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (PUB.BMS.lastSendTime.Year == 1982) PUB.BMS.lastSendTime = DateTime.Now.AddSeconds(1);
|
||||
var ts = DateTime.Now - PUB.BMS.lastSendTime;
|
||||
if (ts.TotalSeconds >= PUB.setting.interval_bms)
|
||||
{
|
||||
PUB.BMS.SendQuery();
|
||||
}
|
||||
Update_BatteryWarnSpeak();
|
||||
PUB.log.AddE($"sm_SPS Exception: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,26 @@ namespace Project
|
||||
}
|
||||
else if (e1.KeyCode == Keys.F5) btAutoRun.PerformClick();
|
||||
else if (e1.KeyCode == Keys.F9) btAutoRun.PerformClick();
|
||||
else if (e1.KeyCode == Keys.F11 && System.Diagnostics.Debugger.IsAttached)
|
||||
{
|
||||
// F11: 모든 스레드 상태 덤프
|
||||
DumpAllThreadsState();
|
||||
}
|
||||
else if (e1.KeyCode == Keys.F12 && System.Diagnostics.Debugger.IsAttached)
|
||||
{
|
||||
// F12: 다음 sm_Running 호출시 디버거 중단
|
||||
RequestDebugBreak = true;
|
||||
var lastCall = DateTime.Now - lastSmRunningTime;
|
||||
PUB.log.Add("DEBUG", $"F12 pressed - 마지막 sm_Running 호출: {lastCall.TotalSeconds:F1}초 전, IsThreadRun: {PUB.sm.IsThreadRun}");
|
||||
System.Windows.Forms.MessageBox.Show(
|
||||
$"다음 sm_Running 호출시 디버거가 중단됩니다.\n\n" +
|
||||
$"마지막 호출: {lastCall.TotalSeconds:F1}초 전\n" +
|
||||
$"IsThreadRun: {PUB.sm.IsThreadRun}\n" +
|
||||
$"Current Step: {PUB.sm.Step}",
|
||||
"디버그 모드",
|
||||
System.Windows.Forms.MessageBoxButtons.OK,
|
||||
System.Windows.Forms.MessageBoxIcon.Information);
|
||||
}
|
||||
|
||||
if (DateTime.Now > PUB.LastInputTime) PUB.LastInputTime = DateTime.Now;
|
||||
};
|
||||
@@ -239,15 +259,67 @@ namespace Project
|
||||
PUB.sm.SetMsgOptOff(); //모든 메세지출력을 해제한다. (이벤트는 동작함)
|
||||
|
||||
PUB.log.Add("State Machine Start");
|
||||
PUB.sm = new StateMachine.StateMachine();
|
||||
|
||||
try
|
||||
{
|
||||
PUB.sm = new StateMachine.StateMachine();
|
||||
PUB.log.Add("StateMachine", $"객체 생성 완료 - Type: {PUB.sm.GetType().FullName}");
|
||||
|
||||
// StateMachine 객체의 속성 확인
|
||||
var smType = PUB.sm.GetType();
|
||||
var properties = smType.GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
|
||||
PUB.log.Add("StateMachine", $"Public Properties: {properties.Length}개");
|
||||
|
||||
var methods = smType.GetMethods(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.DeclaredOnly);
|
||||
PUB.log.Add("StateMachine", $"Public Methods: {methods.Length}개");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PUB.log.AddE($"StateMachine 객체 생성 실패: {ex.Message}");
|
||||
PUB.log.AddE($"StackTrace: {ex.StackTrace}");
|
||||
throw;
|
||||
}
|
||||
|
||||
PUB.sm.StepChanged += sm_StepChanged;
|
||||
PUB.log.Add("StateMachine", "StepChanged 이벤트 등록 완료");
|
||||
|
||||
PUB.sm.Message += sm_Message;
|
||||
PUB.log.Add("StateMachine", "Message 이벤트 등록 완료");
|
||||
|
||||
PUB.sm.Running += sm_Running;
|
||||
PUB.log.Add("StateMachine", "Running 이벤트 등록 완료");
|
||||
|
||||
PUB.sm.SPS += sm_SPS;
|
||||
PUB.log.Add("StateMachine", "SPS 이벤트 등록 완료");
|
||||
|
||||
PUB.sm.Start();
|
||||
PUB.log.Add("StateMachine", $"Start() 호출 완료 - IsThreadRun:{PUB.sm.IsThreadRun}");
|
||||
|
||||
// 스레드 시작 대기 (최대 3초)
|
||||
for (int i = 0; i < 30; i++)
|
||||
{
|
||||
System.Threading.Thread.Sleep(100);
|
||||
if (PUB.sm.IsThreadRun)
|
||||
{
|
||||
PUB.log.Add("StateMachine", $"스레드 시작 확인됨 ({i * 100}ms 소요)");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PUB.sm.IsThreadRun)
|
||||
{
|
||||
PUB.log.AddE( "경고: 3초 대기 후에도 스레드가 시작되지 않음!");
|
||||
System.Windows.Forms.MessageBox.Show(
|
||||
"상태머신 스레드가 시작되지 않았습니다.\n" +
|
||||
"로그 파일을 확인하세요.",
|
||||
"오류",
|
||||
System.Windows.Forms.MessageBoxButtons.OK,
|
||||
System.Windows.Forms.MessageBoxIcon.Error);
|
||||
}
|
||||
|
||||
tmDisplay.Tick += tmDisplay_Tick;
|
||||
tmDisplay.Start(); //start Display
|
||||
PUB.log.Add("Display", "Display Timer 시작 완료");
|
||||
|
||||
this.btDebug.Visible = System.Diagnostics.Debugger.IsAttached || PUB.setting.UseDebugMode;
|
||||
|
||||
@@ -1015,6 +1087,68 @@ namespace Project
|
||||
}
|
||||
}
|
||||
|
||||
private void DumpAllThreadsState()
|
||||
{
|
||||
try
|
||||
{
|
||||
var sb = new System.Text.StringBuilder();
|
||||
sb.AppendLine("===== 스레드 상태 덤프 =====");
|
||||
sb.AppendLine($"시간: {DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}");
|
||||
sb.AppendLine($"마지막 sm_Running 호출: {(DateTime.Now - lastSmRunningTime).TotalSeconds:F1}초 전");
|
||||
sb.AppendLine($"sm_Running 호출 횟수: {sm_Running_CallCount}");
|
||||
sb.AppendLine($"IsThreadRun: {PUB.sm.IsThreadRun}");
|
||||
sb.AppendLine($"Current Step: {PUB.sm.Step}");
|
||||
sb.AppendLine($"Current RunStep: {PUB.sm.RunStep}");
|
||||
sb.AppendLine();
|
||||
|
||||
var process = System.Diagnostics.Process.GetCurrentProcess();
|
||||
sb.AppendLine($"총 스레드 수: {process.Threads.Count}");
|
||||
sb.AppendLine();
|
||||
|
||||
foreach (System.Diagnostics.ProcessThread thread in process.Threads)
|
||||
{
|
||||
sb.AppendLine($"Thread {thread.Id}:");
|
||||
sb.AppendLine($" State: {thread.ThreadState}");
|
||||
sb.AppendLine($" Priority: {thread.PriorityLevel}");
|
||||
if (thread.ThreadState == System.Diagnostics.ThreadState.Wait)
|
||||
{
|
||||
sb.AppendLine($" WaitReason: {thread.WaitReason}");
|
||||
}
|
||||
sb.AppendLine();
|
||||
}
|
||||
|
||||
var dump = sb.ToString();
|
||||
PUB.log.Add("THREAD_DUMP", dump);
|
||||
Console.WriteLine(dump);
|
||||
|
||||
MessageBox.Show(
|
||||
$"스레드 덤프 완료\n로그 파일에 저장되었습니다.\n\n" +
|
||||
$"마지막 sm_Running: {(DateTime.Now - lastSmRunningTime).TotalSeconds:F1}초 전\n" +
|
||||
$"IsThreadRun: {PUB.sm.IsThreadRun}",
|
||||
"스레드 덤프",
|
||||
MessageBoxButtons.OK,
|
||||
MessageBoxIcon.Information);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PUB.log.AddE($"DumpAllThreadsState 오류: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void stateMachineDebugToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
var debugForm = new Dialog.fStateMachineDebug();
|
||||
debugForm.Show();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"디버그 창을 열 수 없습니다:\n{ex.Message}", "오류",
|
||||
MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void editorToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
|
||||
@@ -20,7 +20,27 @@ namespace Project.StateMachine
|
||||
public event EventHandler<StateMachineMessageEventArgs> Message;
|
||||
void RaiseMessage(string header, string msg)
|
||||
{
|
||||
if (Message != null) Message(this, new StateMachineMessageEventArgs(header, msg));
|
||||
if (Message != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 비동기로 이벤트 발생 (블로킹 방지)
|
||||
var handler = Message;
|
||||
if (handler != null)
|
||||
{
|
||||
var args = new StateMachineMessageEventArgs(header, msg);
|
||||
System.Threading.ThreadPool.QueueUserWorkItem(_ =>
|
||||
{
|
||||
try
|
||||
{
|
||||
handler(this, args);
|
||||
}
|
||||
catch { /* 이벤트 핸들러 예외 무시 */ }
|
||||
});
|
||||
}
|
||||
}
|
||||
catch { /* 예외 무시 */ }
|
||||
}
|
||||
}
|
||||
|
||||
public class StepChangeEventArgs : EventArgs
|
||||
|
||||
@@ -87,18 +87,64 @@ namespace Project.StateMachine
|
||||
|
||||
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");
|
||||
while (bLoop)
|
||||
|
||||
try
|
||||
{
|
||||
RunSpeed = DateTime.Now - UpdateTime; //이전업데이트에서 현재 시간의 오차 한바퀴 시간이 표시됨
|
||||
UpdateTime = DateTime.Now;
|
||||
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;
|
||||
|
||||
//항상 작동하는 경우
|
||||
SPS?.Invoke(this, new EventArgs());
|
||||
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)
|
||||
@@ -118,22 +164,47 @@ namespace Project.StateMachine
|
||||
|
||||
|
||||
//동작중에 발생하는 이벤트
|
||||
if (Running != null)
|
||||
var runningHandler = Running;
|
||||
if (runningHandler != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
Running(this, new RunningEventArgs(Step, firstRun, StepRunTime));
|
||||
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", ex.Message);
|
||||
RaiseMessage("SM-ERROR-RUNNING", $"Running Exception: {ex.Message}\n{ex.StackTrace}");
|
||||
}
|
||||
} System.Threading.Thread.Sleep(1);
|
||||
}
|
||||
|
||||
System.Threading.Thread.Sleep(1);
|
||||
}
|
||||
_isthreadrun = false;
|
||||
if (GetMsgOpt(EMsgOpt.NORMAL)) RaiseMessage("SM", "Stop");
|
||||
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}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -175,7 +246,18 @@ namespace Project.StateMachine
|
||||
OldStep = _step;
|
||||
_step = newstep_;
|
||||
_newstep = newstep_;
|
||||
StepChanged?.Invoke(this, new StepChangeEventArgs(OldStep, newstep_));
|
||||
|
||||
// 비동기로 이벤트 발생 (블로킹 방지)
|
||||
var handler = StepChanged;
|
||||
if (handler != null)
|
||||
{
|
||||
var args = new StepChangeEventArgs(OldStep, newstep_);
|
||||
System.Threading.ThreadPool.QueueUserWorkItem(_ =>
|
||||
{
|
||||
try { handler(this, args); }
|
||||
catch { /* 이벤트 핸들러 예외 무시 */ }
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -284,7 +366,18 @@ namespace Project.StateMachine
|
||||
{
|
||||
var ostep = _step;
|
||||
OldStep = _step; _step = _newstep;
|
||||
StepChanged?.Invoke(this, new StepChangeEventArgs(ostep, _step));
|
||||
|
||||
// 비동기로 이벤트 발생 (블로킹 방지)
|
||||
var handler = StepChanged;
|
||||
if (handler != null)
|
||||
{
|
||||
var args = new StepChangeEventArgs(ostep, _step);
|
||||
System.Threading.ThreadPool.QueueUserWorkItem(_ =>
|
||||
{
|
||||
try { handler(this, args); }
|
||||
catch { /* 이벤트 핸들러 예외 무시 */ }
|
||||
});
|
||||
}
|
||||
} //171214
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user