feat: 优化异常处理,新增日志记录
This commit is contained in:
@@ -19,6 +19,12 @@ namespace TimerApp
|
|||||||
private bool _isPaused;
|
private bool _isPaused;
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
|
|
||||||
|
// 状态检测缓存
|
||||||
|
private int _checkTickCounter;
|
||||||
|
private const int CheckIntervalTicks = 3; // 每3秒检查一次空闲/媒体状态
|
||||||
|
private long _cachedIdleMs;
|
||||||
|
private bool _cachedMediaPlaying;
|
||||||
|
|
||||||
// 配置 (默认值)
|
// 配置 (默认值)
|
||||||
public TimeSpan WorkDuration { get; set; } = TimeSpan.FromMinutes(20);
|
public TimeSpan WorkDuration { get; set; } = TimeSpan.FromMinutes(20);
|
||||||
public TimeSpan RestDuration { get; set; } = TimeSpan.FromMinutes(1);
|
public TimeSpan RestDuration { get; set; } = TimeSpan.FromMinutes(1);
|
||||||
@@ -45,6 +51,8 @@ namespace TimerApp
|
|||||||
|
|
||||||
public void Start()
|
public void Start()
|
||||||
{
|
{
|
||||||
|
// 启动时立即触发一次检测
|
||||||
|
_checkTickCounter = CheckIntervalTicks;
|
||||||
_timer.Start();
|
_timer.Start();
|
||||||
ResetWork();
|
ResetWork();
|
||||||
}
|
}
|
||||||
@@ -71,96 +79,116 @@ namespace TimerApp
|
|||||||
|
|
||||||
private void Timer_Tick(object? sender, EventArgs e)
|
private void Timer_Tick(object? sender, EventArgs e)
|
||||||
{
|
{
|
||||||
// 如果处于暂停状态,不处理计时逻辑
|
try
|
||||||
if (_isPaused)
|
|
||||||
{
|
{
|
||||||
return;
|
// 如果处于暂停状态,不处理计时逻辑
|
||||||
}
|
if (_isPaused)
|
||||||
|
|
||||||
long idleMs = NativeMethods.GetIdleTime();
|
|
||||||
TimeSpan idleTime = TimeSpan.FromMilliseconds(idleMs);
|
|
||||||
|
|
||||||
if (CurrentState == MonitorState.Resting)
|
|
||||||
{
|
|
||||||
// 休息模式逻辑
|
|
||||||
// 使用计数器而不是时间差,避免秒数跳变
|
|
||||||
_restElapsedSeconds++;
|
|
||||||
int totalRestSeconds = (int)RestDuration.TotalSeconds;
|
|
||||||
int remainingSeconds = totalRestSeconds - _restElapsedSeconds;
|
|
||||||
|
|
||||||
if (remainingSeconds <= 0)
|
|
||||||
{
|
{
|
||||||
// 休息结束
|
return;
|
||||||
RestEnded?.Invoke(this, EventArgs.Empty);
|
|
||||||
ResetWork(); // 重新开始工作周期
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
long idleMs;
|
||||||
|
TimeSpan idleTime;
|
||||||
|
|
||||||
|
// 优化:降低系统API调用频率
|
||||||
|
// 每 CheckIntervalTicks (3) 秒更新一次状态
|
||||||
|
_checkTickCounter++;
|
||||||
|
if (_checkTickCounter >= CheckIntervalTicks)
|
||||||
{
|
{
|
||||||
TimeSpan remainingRest = TimeSpan.FromSeconds(remainingSeconds);
|
_checkTickCounter = 0;
|
||||||
RestProgressChanged?.Invoke(this, remainingRest);
|
_cachedIdleMs = NativeMethods.GetIdleTime();
|
||||||
|
_cachedMediaPlaying = NativeMethods.IsMediaPlaying();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// 检测是否有视频/媒体正在播放
|
|
||||||
bool isMediaPlaying = NativeMethods.IsMediaPlaying();
|
|
||||||
|
|
||||||
// 工作/空闲模式逻辑
|
idleMs = _cachedIdleMs;
|
||||||
if (idleTime > IdleThreshold || isMediaPlaying)
|
idleTime = TimeSpan.FromMilliseconds(idleMs);
|
||||||
|
|
||||||
|
if (CurrentState == MonitorState.Resting)
|
||||||
{
|
{
|
||||||
// 用户离开了或正在播放视频
|
// 休息模式逻辑
|
||||||
if (CurrentState == MonitorState.Working)
|
// 使用计数器而不是时间差,避免秒数跳变
|
||||||
{
|
_restElapsedSeconds++;
|
||||||
// 如果正在工作,但离开了,暂停工作计时?
|
int totalRestSeconds = (int)RestDuration.TotalSeconds;
|
||||||
// 简单起见,如果离开时间过长,可以视为一种“休息”,或者只是暂停累积
|
int remainingSeconds = totalRestSeconds - _restElapsedSeconds;
|
||||||
// 这里我们简单处理:如果空闲时间超过阈值,状态变为空闲
|
|
||||||
ChangeState(MonitorState.Idle);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果在 Idle 状态,且空闲时间非常长(比如超过了休息时间),
|
if (remainingSeconds <= 0)
|
||||||
// 是否应该重置工作计时器?
|
|
||||||
// 假设用户去开会了1小时,回来应该重新计算20分钟。
|
|
||||||
if (idleTime > RestDuration)
|
|
||||||
{
|
{
|
||||||
_accumulatedWorkTime = TimeSpan.Zero;
|
// 休息结束
|
||||||
}
|
RestEnded?.Invoke(this, EventArgs.Empty);
|
||||||
|
ResetWork(); // 重新开始工作周期
|
||||||
// 如果正在播放视频,不累加工作时间,但保持当前状态
|
|
||||||
if (isMediaPlaying && CurrentState == MonitorState.Working)
|
|
||||||
{
|
|
||||||
// 保持当前剩余时间不变(不累加,也不减少)
|
|
||||||
TimeSpan remainingWork = WorkDuration - _accumulatedWorkTime;
|
|
||||||
WorkProgressChanged?.Invoke(this, remainingWork);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// 用户在活动且没有播放视频
|
|
||||||
if (CurrentState == MonitorState.Idle)
|
|
||||||
{
|
|
||||||
// 从空闲变为工作
|
|
||||||
ChangeState(MonitorState.Working);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 累加工作时间
|
|
||||||
// 简单的累加逻辑:这一秒是工作的
|
|
||||||
_accumulatedWorkTime += TimeSpan.FromSeconds(1);
|
|
||||||
|
|
||||||
// 检查是否达到工作时长
|
|
||||||
TimeSpan remainingWork = WorkDuration - _accumulatedWorkTime;
|
|
||||||
|
|
||||||
if (remainingWork <= TimeSpan.Zero)
|
|
||||||
{
|
|
||||||
// 触发休息
|
|
||||||
_restElapsedSeconds = 0; // 重置休息计数器
|
|
||||||
ChangeState(MonitorState.Resting);
|
|
||||||
RestStarted?.Invoke(this, EventArgs.Empty);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
WorkProgressChanged?.Invoke(this, remainingWork);
|
TimeSpan remainingRest = TimeSpan.FromSeconds(remainingSeconds);
|
||||||
|
RestProgressChanged?.Invoke(this, remainingRest);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 使用缓存的媒体播放状态
|
||||||
|
bool isMediaPlaying = _cachedMediaPlaying;
|
||||||
|
|
||||||
|
// 工作/空闲模式逻辑
|
||||||
|
if (idleTime > IdleThreshold || isMediaPlaying)
|
||||||
|
{
|
||||||
|
// 用户离开了或正在播放视频
|
||||||
|
if (CurrentState == MonitorState.Working)
|
||||||
|
{
|
||||||
|
// 如果正在工作,但离开了,暂停工作计时?
|
||||||
|
// 简单起见,如果离开时间过长,可以视为一种“休息”,或者只是暂停累积
|
||||||
|
// 这里我们简单处理:如果空闲时间超过阈值,状态变为空闲
|
||||||
|
ChangeState(MonitorState.Idle);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果在 Idle 状态,且空闲时间非常长(比如超过了休息时间),
|
||||||
|
// 是否应该重置工作计时器?
|
||||||
|
// 假设用户去开会了1小时,回来应该重新计算20分钟。
|
||||||
|
if (idleTime > RestDuration)
|
||||||
|
{
|
||||||
|
_accumulatedWorkTime = TimeSpan.Zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果正在播放视频,不累加工作时间,但保持当前状态
|
||||||
|
if (isMediaPlaying && CurrentState == MonitorState.Working)
|
||||||
|
{
|
||||||
|
// 保持当前剩余时间不变(不累加,也不减少)
|
||||||
|
TimeSpan remainingWork = WorkDuration - _accumulatedWorkTime;
|
||||||
|
WorkProgressChanged?.Invoke(this, remainingWork);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 用户在活动且没有播放视频
|
||||||
|
if (CurrentState == MonitorState.Idle)
|
||||||
|
{
|
||||||
|
// 从空闲变为工作
|
||||||
|
ChangeState(MonitorState.Working);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 累加工作时间
|
||||||
|
// 简单的累加逻辑:这一秒是工作的
|
||||||
|
_accumulatedWorkTime += TimeSpan.FromSeconds(1);
|
||||||
|
|
||||||
|
// 检查是否达到工作时长
|
||||||
|
TimeSpan remainingWork = WorkDuration - _accumulatedWorkTime;
|
||||||
|
|
||||||
|
if (remainingWork <= TimeSpan.Zero)
|
||||||
|
{
|
||||||
|
// 触发休息
|
||||||
|
_restElapsedSeconds = 0; // 重置休息计数器
|
||||||
|
ChangeState(MonitorState.Resting);
|
||||||
|
RestStarted?.Invoke(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
WorkProgressChanged?.Invoke(this, remainingWork);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogError("Error in ActivityMonitor Timer_Tick", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
36
Logger.cs
Normal file
36
Logger.cs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace TimerApp
|
||||||
|
{
|
||||||
|
public static class Logger
|
||||||
|
{
|
||||||
|
private static readonly string LogFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "error.log");
|
||||||
|
private static readonly object LockObj = new object();
|
||||||
|
|
||||||
|
public static void LogError(string message, Exception? ex = null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
lock (LockObj)
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
sb.AppendLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] ERROR: {message}");
|
||||||
|
if (ex != null)
|
||||||
|
{
|
||||||
|
sb.AppendLine($"Exception: {ex.Message}");
|
||||||
|
sb.AppendLine($"StackTrace: {ex.StackTrace}");
|
||||||
|
}
|
||||||
|
sb.AppendLine(new string('-', 50));
|
||||||
|
|
||||||
|
File.AppendAllText(LogFile, sb.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Failed to log, nothing we can do
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
57
Program.cs
57
Program.cs
@@ -5,24 +5,51 @@ static class Program
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The main entry point for the application.
|
/// The main entry point for the application.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[STAThread]
|
[STAThread]
|
||||||
static void Main()
|
static void Main()
|
||||||
{
|
|
||||||
if (!SingleInstanceManager.TryAcquire(out var instance) || instance is null)
|
|
||||||
{
|
{
|
||||||
SingleInstanceManager.SignalExistingInstance();
|
// 设置全局异常处理
|
||||||
return;
|
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
|
||||||
|
Application.ThreadException += Application_ThreadException;
|
||||||
|
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
|
||||||
|
|
||||||
|
if (!SingleInstanceManager.TryAcquire(out var instance) || instance is null)
|
||||||
|
{
|
||||||
|
SingleInstanceManager.SignalExistingInstance();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (instance)
|
||||||
|
{
|
||||||
|
TaskbarIntegration.InitializeProcess();
|
||||||
|
ApplicationConfiguration.Initialize();
|
||||||
|
TaskbarIntegration.InitializeShortcuts();
|
||||||
|
|
||||||
|
var mainForm = new MainForm();
|
||||||
|
instance.StartServer(mainForm.ActivateFromExternal);
|
||||||
|
Application.Run(mainForm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogError("Fatal error in Main", ex);
|
||||||
|
MessageBox.Show($"程序发生严重错误即将退出:\n{ex.Message}", "TimerApp Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
using (instance)
|
private static void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e)
|
||||||
{
|
{
|
||||||
TaskbarIntegration.InitializeProcess();
|
Logger.LogError("Unhandled UI Exception", e.Exception);
|
||||||
ApplicationConfiguration.Initialize();
|
// 这里可以选择不退出,或者提示用户
|
||||||
TaskbarIntegration.InitializeShortcuts();
|
// MessageBox.Show("发生未知错误,程序将尝试继续运行。", "错误", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||||
|
|
||||||
var mainForm = new MainForm();
|
|
||||||
instance.StartServer(mainForm.ActivateFromExternal);
|
|
||||||
Application.Run(mainForm);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
|
||||||
|
{
|
||||||
|
var ex = e.ExceptionObject as Exception;
|
||||||
|
Logger.LogError("Unhandled Domain Exception" + (e.IsTerminating ? " (Terminating)" : ""), ex);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user