Fix: 상태머신 루프 블로킹 문제 수정 - SPS 이벤트 핸들러 비동기 처리 및 타임아웃 보호 추가
- sm_SPS 이벤트 핸들러에서 장치 연결 및 상태 전송을 비동기로 처리 - DeviceConnectionWorker 스레드로 장치 연결 분리 - SPS(1초), Running(2초) 타임아웃 보호 추가 - 상태머신 모니터링 디버그 창 추가 (fStateMachineDebug) - F11/F12 단축키로 스레드 덤프 및 디버그 브레이크 지원 - RaiseMessage 이벤트 비동기 처리로 로그 블로킹 방지
This commit is contained in:
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user