fix: 修复长时间运行后的卡死问题
This commit is contained in:
@@ -13,7 +13,8 @@ namespace TimerApp
|
|||||||
|
|
||||||
public sealed class ActivityMonitor : IDisposable
|
public sealed class ActivityMonitor : IDisposable
|
||||||
{
|
{
|
||||||
private readonly System.Windows.Forms.Timer _timer;
|
private CancellationTokenSource? _cts;
|
||||||
|
private readonly object _lock = new object();
|
||||||
private TimeSpan _accumulatedWorkTime;
|
private TimeSpan _accumulatedWorkTime;
|
||||||
private int _restElapsedSeconds;
|
private int _restElapsedSeconds;
|
||||||
private bool _isPaused;
|
private bool _isPaused;
|
||||||
@@ -31,7 +32,11 @@ namespace TimerApp
|
|||||||
public TimeSpan IdleThreshold { get; set; } = TimeSpan.FromSeconds(30);
|
public TimeSpan IdleThreshold { get; set; } = TimeSpan.FromSeconds(30);
|
||||||
|
|
||||||
public MonitorState CurrentState { get; private set; } = MonitorState.Idle;
|
public MonitorState CurrentState { get; private set; } = MonitorState.Idle;
|
||||||
public bool IsPaused { get; private set; } = false;
|
public bool IsPaused
|
||||||
|
{
|
||||||
|
get { lock (_lock) return _isPaused; }
|
||||||
|
private set { lock (_lock) _isPaused = value; }
|
||||||
|
}
|
||||||
|
|
||||||
// 事件
|
// 事件
|
||||||
public event EventHandler<TimeSpan>? WorkProgressChanged; // 剩余工作时间
|
public event EventHandler<TimeSpan>? WorkProgressChanged; // 剩余工作时间
|
||||||
@@ -42,34 +47,54 @@ namespace TimerApp
|
|||||||
|
|
||||||
public ActivityMonitor()
|
public ActivityMonitor()
|
||||||
{
|
{
|
||||||
_timer = new System.Windows.Forms.Timer();
|
|
||||||
_timer.Interval = 1000; // 1秒检查一次
|
|
||||||
_timer.Tick += Timer_Tick;
|
|
||||||
|
|
||||||
SystemEvents.PowerModeChanged += OnPowerModeChanged;
|
SystemEvents.PowerModeChanged += OnPowerModeChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Start()
|
public void Start()
|
||||||
{
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
StopInternal();
|
||||||
|
|
||||||
|
_cts = new CancellationTokenSource();
|
||||||
|
var token = _cts.Token;
|
||||||
|
|
||||||
// 启动时立即触发一次检测
|
// 启动时立即触发一次检测
|
||||||
_checkTickCounter = CheckIntervalTicks;
|
_checkTickCounter = CheckIntervalTicks;
|
||||||
_timer.Start();
|
|
||||||
ResetWork();
|
ResetWork();
|
||||||
|
|
||||||
|
Task.Run(() => MonitorLoop(token), token);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Stop()
|
public void Stop()
|
||||||
{
|
{
|
||||||
_timer.Stop();
|
lock (_lock)
|
||||||
|
{
|
||||||
|
StopInternal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StopInternal()
|
||||||
|
{
|
||||||
|
if (_cts != null)
|
||||||
|
{
|
||||||
|
_cts.Cancel();
|
||||||
|
_cts.Dispose();
|
||||||
|
_cts = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ResetWork()
|
private void ResetWork()
|
||||||
{
|
{
|
||||||
|
// Must be called within lock
|
||||||
_accumulatedWorkTime = TimeSpan.Zero;
|
_accumulatedWorkTime = TimeSpan.Zero;
|
||||||
ChangeState(MonitorState.Idle);
|
ChangeState(MonitorState.Idle);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ChangeState(MonitorState newState)
|
private void ChangeState(MonitorState newState)
|
||||||
{
|
{
|
||||||
|
// Must be called within lock
|
||||||
if (CurrentState != newState)
|
if (CurrentState != newState)
|
||||||
{
|
{
|
||||||
CurrentState = newState;
|
CurrentState = newState;
|
||||||
@@ -77,9 +102,29 @@ namespace TimerApp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Timer_Tick(object? sender, EventArgs e)
|
private async Task MonitorLoop(CancellationToken token)
|
||||||
|
{
|
||||||
|
while (!token.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
{
|
||||||
|
await Task.Delay(1000, token);
|
||||||
|
OnTick();
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogError("Error in ActivityMonitor MonitorLoop", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnTick()
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
{
|
{
|
||||||
// 如果处于暂停状态,不处理计时逻辑
|
// 如果处于暂停状态,不处理计时逻辑
|
||||||
if (_isPaused)
|
if (_isPaused)
|
||||||
@@ -134,15 +179,12 @@ namespace TimerApp
|
|||||||
// 用户离开了或正在播放视频
|
// 用户离开了或正在播放视频
|
||||||
if (CurrentState == MonitorState.Working)
|
if (CurrentState == MonitorState.Working)
|
||||||
{
|
{
|
||||||
// 如果正在工作,但离开了,暂停工作计时?
|
// 如果空闲时间超过阈值,状态变为空闲
|
||||||
// 简单起见,如果离开时间过长,可以视为一种“休息”,或者只是暂停累积
|
|
||||||
// 这里我们简单处理:如果空闲时间超过阈值,状态变为空闲
|
|
||||||
ChangeState(MonitorState.Idle);
|
ChangeState(MonitorState.Idle);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果在 Idle 状态,且空闲时间非常长(比如超过了休息时间),
|
// 如果在 Idle 状态,且空闲时间非常长(比如超过了休息时间),
|
||||||
// 是否应该重置工作计时器?
|
// 重置工作计时器
|
||||||
// 假设用户去开会了1小时,回来应该重新计算20分钟。
|
|
||||||
if (idleTime > RestDuration)
|
if (idleTime > RestDuration)
|
||||||
{
|
{
|
||||||
_accumulatedWorkTime = TimeSpan.Zero;
|
_accumulatedWorkTime = TimeSpan.Zero;
|
||||||
@@ -151,7 +193,6 @@ namespace TimerApp
|
|||||||
// 如果正在播放视频,不累加工作时间,但保持当前状态
|
// 如果正在播放视频,不累加工作时间,但保持当前状态
|
||||||
if (isMediaPlaying && CurrentState == MonitorState.Working)
|
if (isMediaPlaying && CurrentState == MonitorState.Working)
|
||||||
{
|
{
|
||||||
// 保持当前剩余时间不变(不累加,也不减少)
|
|
||||||
TimeSpan remainingWork = WorkDuration - _accumulatedWorkTime;
|
TimeSpan remainingWork = WorkDuration - _accumulatedWorkTime;
|
||||||
WorkProgressChanged?.Invoke(this, remainingWork);
|
WorkProgressChanged?.Invoke(this, remainingWork);
|
||||||
}
|
}
|
||||||
@@ -166,7 +207,6 @@ namespace TimerApp
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 累加工作时间
|
// 累加工作时间
|
||||||
// 简单的累加逻辑:这一秒是工作的
|
|
||||||
_accumulatedWorkTime += TimeSpan.FromSeconds(1);
|
_accumulatedWorkTime += TimeSpan.FromSeconds(1);
|
||||||
|
|
||||||
// 检查是否达到工作时长
|
// 检查是否达到工作时长
|
||||||
@@ -186,13 +226,11 @@ namespace TimerApp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logger.LogError("Error in ActivityMonitor Timer_Tick", ex);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RefreshStatus()
|
public void RefreshStatus()
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
{
|
{
|
||||||
if (CurrentState == MonitorState.Working)
|
if (CurrentState == MonitorState.Working)
|
||||||
{
|
{
|
||||||
@@ -201,7 +239,6 @@ namespace TimerApp
|
|||||||
}
|
}
|
||||||
else if (CurrentState == MonitorState.Resting)
|
else if (CurrentState == MonitorState.Resting)
|
||||||
{
|
{
|
||||||
// 使用计数器计算剩余时间,保持一致性
|
|
||||||
int totalRestSeconds = (int)RestDuration.TotalSeconds;
|
int totalRestSeconds = (int)RestDuration.TotalSeconds;
|
||||||
int remainingSeconds = totalRestSeconds - _restElapsedSeconds;
|
int remainingSeconds = totalRestSeconds - _restElapsedSeconds;
|
||||||
if (remainingSeconds < 0) remainingSeconds = 0;
|
if (remainingSeconds < 0) remainingSeconds = 0;
|
||||||
@@ -209,15 +246,21 @@ namespace TimerApp
|
|||||||
RestProgressChanged?.Invoke(this, remaining);
|
RestProgressChanged?.Invoke(this, remaining);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void Restart()
|
public void Restart()
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
{
|
{
|
||||||
_accumulatedWorkTime = TimeSpan.Zero;
|
_accumulatedWorkTime = TimeSpan.Zero;
|
||||||
_isPaused = false;
|
_isPaused = false;
|
||||||
IsPaused = false;
|
|
||||||
|
|
||||||
// Ensure timer is running
|
// Ensure task is running
|
||||||
if (!_timer.Enabled) _timer.Start();
|
if (_cts == null || _cts.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
// Re-start if stopped (though Restart implies it's running)
|
||||||
|
// Usually Start() calls ResetWork, so we just reset here
|
||||||
|
}
|
||||||
|
|
||||||
// Force state to Working since user manually restarted
|
// Force state to Working since user manually restarted
|
||||||
ChangeState(MonitorState.Working);
|
ChangeState(MonitorState.Working);
|
||||||
@@ -225,29 +268,32 @@ namespace TimerApp
|
|||||||
// Immediately refresh UI to show full duration
|
// Immediately refresh UI to show full duration
|
||||||
RefreshStatus();
|
RefreshStatus();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void Pause()
|
public void Pause()
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
{
|
{
|
||||||
if (!_isPaused && CurrentState != MonitorState.Idle)
|
if (!_isPaused && CurrentState != MonitorState.Idle)
|
||||||
{
|
{
|
||||||
_isPaused = true;
|
_isPaused = true;
|
||||||
IsPaused = true;
|
|
||||||
|
|
||||||
StateChanged?.Invoke(this, EventArgs.Empty);
|
StateChanged?.Invoke(this, EventArgs.Empty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void Resume()
|
public void Resume()
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
{
|
{
|
||||||
if (_isPaused)
|
if (_isPaused)
|
||||||
{
|
{
|
||||||
_isPaused = false;
|
_isPaused = false;
|
||||||
IsPaused = false;
|
|
||||||
|
|
||||||
StateChanged?.Invoke(this, EventArgs.Empty);
|
StateChanged?.Invoke(this, EventArgs.Empty);
|
||||||
RefreshStatus();
|
RefreshStatus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
@@ -255,10 +301,7 @@ namespace TimerApp
|
|||||||
_disposed = true;
|
_disposed = true;
|
||||||
|
|
||||||
SystemEvents.PowerModeChanged -= OnPowerModeChanged;
|
SystemEvents.PowerModeChanged -= OnPowerModeChanged;
|
||||||
|
Stop();
|
||||||
_timer.Stop();
|
|
||||||
_timer.Tick -= Timer_Tick;
|
|
||||||
_timer.Dispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPowerModeChanged(object sender, PowerModeChangedEventArgs e)
|
private void OnPowerModeChanged(object sender, PowerModeChangedEventArgs e)
|
||||||
|
|||||||
30
MainForm.cs
30
MainForm.cs
@@ -412,7 +412,7 @@ namespace TimerApp
|
|||||||
{
|
{
|
||||||
if (InvokeRequired)
|
if (InvokeRequired)
|
||||||
{
|
{
|
||||||
Invoke(new Action<object?, EventArgs>(Monitor_StateChanged), sender, e);
|
BeginInvoke(new Action<object?, EventArgs>(Monitor_StateChanged), sender, e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
UpdateStatusUI();
|
UpdateStatusUI();
|
||||||
@@ -461,19 +461,25 @@ namespace TimerApp
|
|||||||
{
|
{
|
||||||
if (InvokeRequired)
|
if (InvokeRequired)
|
||||||
{
|
{
|
||||||
Invoke(new Action<object?, TimeSpan>(Monitor_WorkProgressChanged), sender, remaining);
|
BeginInvoke(new Action<object?, TimeSpan>(Monitor_WorkProgressChanged), sender, remaining);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
lblTimeLeft.Text = $"{remaining.Minutes:D2}:{remaining.Seconds:D2}";
|
lblTimeLeft.Text = $"{remaining.Minutes:D2}:{remaining.Seconds:D2}";
|
||||||
|
|
||||||
// Update tray tooltip
|
// Update tray tooltip
|
||||||
|
string newText;
|
||||||
if (remaining.TotalMinutes < 1)
|
if (remaining.TotalMinutes < 1)
|
||||||
{
|
{
|
||||||
notifyIcon1.Text = $"即将休息: {remaining.Seconds}秒";
|
newText = $"即将休息: {remaining.Seconds}秒";
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
notifyIcon1.Text = $"工作中: 剩余 {remaining.Minutes} 分钟";
|
newText = $"工作中: 剩余 {remaining.Minutes} 分钟";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (notifyIcon1.Text != newText)
|
||||||
|
{
|
||||||
|
notifyIcon1.Text = newText;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -481,7 +487,7 @@ namespace TimerApp
|
|||||||
{
|
{
|
||||||
if (InvokeRequired)
|
if (InvokeRequired)
|
||||||
{
|
{
|
||||||
Invoke(new Action<object?, EventArgs>(Monitor_RestStarted), sender, e);
|
BeginInvoke(new Action<object?, EventArgs>(Monitor_RestStarted), sender, e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -503,6 +509,12 @@ namespace TimerApp
|
|||||||
|
|
||||||
private void Monitor_RestProgressChanged(object? sender, TimeSpan remaining)
|
private void Monitor_RestProgressChanged(object? sender, TimeSpan remaining)
|
||||||
{
|
{
|
||||||
|
if (InvokeRequired)
|
||||||
|
{
|
||||||
|
BeginInvoke(new Action<object?, TimeSpan>(Monitor_RestProgressChanged), sender, remaining);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (_restForm != null && !_restForm.IsDisposed && _restForm.Visible)
|
if (_restForm != null && !_restForm.IsDisposed && _restForm.Visible)
|
||||||
{
|
{
|
||||||
_restForm.UpdateTime(remaining);
|
_restForm.UpdateTime(remaining);
|
||||||
@@ -513,7 +525,7 @@ namespace TimerApp
|
|||||||
{
|
{
|
||||||
if (InvokeRequired)
|
if (InvokeRequired)
|
||||||
{
|
{
|
||||||
Invoke(new Action<object?, EventArgs>(Monitor_RestEnded), sender, e);
|
BeginInvoke(new Action<object?, EventArgs>(Monitor_RestEnded), sender, e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -568,9 +580,15 @@ namespace TimerApp
|
|||||||
|
|
||||||
private void ShowForm()
|
private void ShowForm()
|
||||||
{
|
{
|
||||||
|
if (this.IsDisposed) return;
|
||||||
|
|
||||||
this.Show();
|
this.Show();
|
||||||
|
if (this.WindowState == FormWindowState.Minimized)
|
||||||
|
{
|
||||||
this.WindowState = FormWindowState.Normal;
|
this.WindowState = FormWindowState.Normal;
|
||||||
|
}
|
||||||
this.Activate();
|
this.Activate();
|
||||||
|
this.BringToFront();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ActivateFromExternal()
|
public void ActivateFromExternal()
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ namespace TimerApp
|
|||||||
{
|
{
|
||||||
if (lblTimer.InvokeRequired)
|
if (lblTimer.InvokeRequired)
|
||||||
{
|
{
|
||||||
lblTimer.Invoke(new Action<TimeSpan>(UpdateTime), remaining);
|
lblTimer.BeginInvoke(new Action<TimeSpan>(UpdateTime), remaining);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user