diff --git a/BetterGenshinImpact/GameTask/AutoGeniusInvokation/GeniusInvokationControl.cs b/BetterGenshinImpact/GameTask/AutoGeniusInvokation/GeniusInvokationControl.cs
index 6d635c53..27b096c0 100644
--- a/BetterGenshinImpact/GameTask/AutoGeniusInvokation/GeniusInvokationControl.cs
+++ b/BetterGenshinImpact/GameTask/AutoGeniusInvokation/GeniusInvokationControl.cs
@@ -101,20 +101,14 @@ public class GeniusInvokationControl
public void CheckTask()
{
- NewRetry.Do(() =>
+ if (_ct is { IsCancellationRequested: true })
{
- if (_ct is { IsCancellationRequested: true })
- {
- return;
- }
+ throw new TaskCanceledException("任务取消");
+ }
- TaskControl.TrySuspend();
- if (!SystemControl.IsGenshinImpactActiveByProcess())
- {
- _logger.LogWarning("当前获取焦点的窗口不是原神,暂停");
- throw new RetryException("当前获取焦点的窗口不是原神");
- }
- }, TimeSpan.FromSeconds(1), 100);
+ // The new PausableDelayManager already handles suspend checking and window focus
+ // We can just trigger it with a minimal sleep
+ TaskControl.CheckAndSleep(0);
if (_ct is { IsCancellationRequested: true })
{
diff --git a/BetterGenshinImpact/GameTask/Common/PausableDelayManager.cs b/BetterGenshinImpact/GameTask/Common/PausableDelayManager.cs
new file mode 100644
index 00000000..189fc740
--- /dev/null
+++ b/BetterGenshinImpact/GameTask/Common/PausableDelayManager.cs
@@ -0,0 +1,230 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using BetterGenshinImpact.GameTask.AutoGeniusInvokation.Exception;
+using Microsoft.Extensions.Logging;
+using Vanara.PInvoke;
+
+namespace BetterGenshinImpact.GameTask.Common;
+
+///
+/// Manages pausable delays with suspend/resume functionality and window focus checking.
+/// Uses event-based synchronization for efficient waiting instead of busy polling.
+///
+public class PausableDelayManager
+{
+ private static readonly ILogger Logger = App.GetLogger();
+
+ ///
+ /// Performs a pausable sleep with suspend checking and window focus validation.
+ ///
+ /// Time to sleep in milliseconds
+ public void Sleep(int millisecondsTimeout)
+ {
+ if (millisecondsTimeout <= 0)
+ {
+ return;
+ }
+
+ // Check and handle suspend/window focus before sleeping
+ CheckAndHandleSuspend();
+ CheckAndActivateGameWindow();
+
+ // Perform the actual sleep with periodic pause checks
+ SleepWithPauseCheck(millisecondsTimeout);
+ }
+
+ ///
+ /// Performs a pausable sleep with cancellation support.
+ ///
+ /// Time to sleep in milliseconds
+ /// Cancellation token
+ public void Sleep(int millisecondsTimeout, CancellationToken ct)
+ {
+ ct.ThrowIfCancellationRequested();
+
+ if (millisecondsTimeout <= 0)
+ {
+ return;
+ }
+
+ // Check and handle suspend/window focus before sleeping
+ CheckAndHandleSuspend();
+ CheckAndActivateGameWindow();
+
+ // Perform the actual sleep with periodic pause checks
+ SleepWithPauseCheck(millisecondsTimeout, ct);
+
+ ct.ThrowIfCancellationRequested();
+ }
+
+ ///
+ /// Performs a pausable async delay with cancellation support.
+ ///
+ /// Time to delay in milliseconds
+ /// Cancellation token
+ public async Task DelayAsync(int millisecondsTimeout, CancellationToken ct)
+ {
+ ct.ThrowIfCancellationRequested();
+
+ if (millisecondsTimeout <= 0)
+ {
+ return;
+ }
+
+ // Check and handle suspend/window focus before delaying
+ CheckAndHandleSuspend();
+ CheckAndActivateGameWindow();
+
+ // Perform the actual delay with periodic pause checks
+ await DelayWithPauseCheckAsync(millisecondsTimeout, ct);
+
+ ct.ThrowIfCancellationRequested();
+ }
+
+ ///
+ /// Checks and handles suspend state, blocking until resume if needed.
+ /// Uses a simple polling approach with efficient waiting.
+ ///
+ private void CheckAndHandleSuspend()
+ {
+ var first = true;
+ var isSuspend = RunnerContext.Instance.IsSuspend;
+
+ while (RunnerContext.Instance.IsSuspend)
+ {
+ if (first)
+ {
+ RunnerContext.Instance.StopAutoPick();
+
+ // Release any pressed keys
+ Thread.Sleep(300);
+ ReleaseAllPressedKeys();
+
+ Logger.LogWarning("快捷键触发暂停,等待解除");
+
+ // Suspend all registered suspendables
+ foreach (var item in RunnerContext.Instance.SuspendableDictionary)
+ {
+ item.Value.Suspend();
+ }
+
+ first = false;
+ }
+
+ // Wait before checking again
+ Thread.Sleep(1000);
+ }
+
+ // Resume from suspend
+ if (isSuspend)
+ {
+ Logger.LogWarning("暂停已经解除");
+ RunnerContext.Instance.ResumeAutoPick();
+
+ // Resume all registered suspendables
+ foreach (var item in RunnerContext.Instance.SuspendableDictionary)
+ {
+ item.Value.Resume();
+ }
+ }
+ }
+
+ private void CheckAndActivateGameWindow()
+ {
+ if (!TaskContext.Instance().Config.OtherConfig.RestoreFocusOnLostEnabled)
+ {
+ if (!SystemControl.IsGenshinImpactActiveByProcess())
+ {
+ Logger.LogInformation("当前获取焦点的窗口不是原神,暂停");
+ throw new RetryException("当前获取焦点的窗口不是原神");
+ }
+ }
+
+ var count = 0;
+ while (!SystemControl.IsGenshinImpactActiveByProcess())
+ {
+ if (count >= 10 && count % 10 == 0)
+ {
+ Logger.LogInformation("多次尝试未恢复,尝试最小化后激活窗口!");
+ SystemControl.MinimizeAndActivateWindow(TaskContext.Instance().GameHandle);
+ }
+ else
+ {
+ Logger.LogInformation("当前获取焦点的窗口不是原神,尝试恢复窗口");
+ SystemControl.FocusWindow(TaskContext.Instance().GameHandle);
+ }
+
+ count++;
+ Thread.Sleep(1000);
+ }
+ }
+
+ ///
+ /// Sleeps with periodic pause state checks using event-based waiting.
+ ///
+ private void SleepWithPauseCheck(int millisecondsTimeout, CancellationToken ct = default)
+ {
+ var remaining = millisecondsTimeout;
+ var checkInterval = Math.Min(1000, millisecondsTimeout); // Check every 1 second or less
+
+ while (remaining > 0)
+ {
+ ct.ThrowIfCancellationRequested();
+
+ var sleepTime = Math.Min(checkInterval, remaining);
+ Thread.Sleep(sleepTime);
+ remaining -= sleepTime;
+
+ // Check if we need to suspend
+ if (RunnerContext.Instance.IsSuspend)
+ {
+ CheckAndHandleSuspend();
+ CheckAndActivateGameWindow();
+ }
+ }
+ }
+
+ ///
+ /// Async delay with periodic pause state checks using event-based waiting.
+ ///
+ private async Task DelayWithPauseCheckAsync(int millisecondsTimeout, CancellationToken ct)
+ {
+ var remaining = millisecondsTimeout;
+ var checkInterval = Math.Min(1000, millisecondsTimeout); // Check every 1 second or less
+
+ while (remaining > 0)
+ {
+ ct.ThrowIfCancellationRequested();
+
+ var delayTime = Math.Min(checkInterval, remaining);
+ await Task.Delay(delayTime, ct);
+ remaining -= delayTime;
+
+ // Check if we need to suspend
+ if (RunnerContext.Instance.IsSuspend)
+ {
+ CheckAndHandleSuspend();
+ CheckAndActivateGameWindow();
+ }
+ }
+ }
+
+ private static bool IsKeyPressed(User32.VK key)
+ {
+ var state = User32.GetAsyncKeyState((int)key);
+ return (state & 0x8000) != 0;
+ }
+
+ private static void ReleaseAllPressedKeys()
+ {
+ foreach (User32.VK key in Enum.GetValues(typeof(User32.VK)))
+ {
+ if (IsKeyPressed(key))
+ {
+ Logger.LogWarning($"解除{key}的按下状态.");
+ Core.Simulator.Simulation.SendInput.Keyboard.KeyUp(key);
+ }
+ }
+ }
+}
diff --git a/BetterGenshinImpact/GameTask/Common/TaskControl.cs b/BetterGenshinImpact/GameTask/Common/TaskControl.cs
index a23092fa..5b9ebb88 100644
--- a/BetterGenshinImpact/GameTask/Common/TaskControl.cs
+++ b/BetterGenshinImpact/GameTask/Common/TaskControl.cs
@@ -18,25 +18,20 @@ public class TaskControl
public static readonly SemaphoreSlim TaskSemaphore = new(1, 1);
+ private static readonly PausableDelayManager _delayManager = new();
+
public static void CheckAndSleep(int millisecondsTimeout)
{
- TrySuspend();
- CheckAndActivateGameWindow();
-
- Thread.Sleep(millisecondsTimeout);
+ _delayManager.Sleep(millisecondsTimeout);
}
public static void Sleep(int millisecondsTimeout)
{
- NewRetry.Do(() =>
- {
- TrySuspend();
- CheckAndActivateGameWindow();
- }, TimeSpan.FromSeconds(1), 100);
- Thread.Sleep(millisecondsTimeout);
+ _delayManager.Sleep(millisecondsTimeout);
}
+ [Obsolete("Use CheckAndSleep or Sleep instead")]
private static bool IsKeyPressed(User32.VK key)
{
// 获取按键状态
@@ -46,6 +41,7 @@ public class TaskControl
return (state & 0x8000) != 0;
}
+ [Obsolete("This method is now handled internally by PausableDelayManager")]
public static void TrySuspend()
{
@@ -93,6 +89,7 @@ public class TaskControl
}
}
+ [Obsolete("This method is now handled internally by PausableDelayManager")]
private static void CheckAndActivateGameWindow()
{
if (!TaskContext.Instance().Config.OtherConfig.RestoreFocusOnLostEnabled)
@@ -131,26 +128,7 @@ public class TaskControl
throw new NormalEndException("取消自动任务");
}
- if (millisecondsTimeout <= 0)
- {
- return;
- }
-
- NewRetry.Do(() =>
- {
- if (ct.IsCancellationRequested)
- {
- throw new NormalEndException("取消自动任务");
- }
-
- TrySuspend();
- CheckAndActivateGameWindow();
- }, TimeSpan.FromSeconds(1), 100);
- Thread.Sleep(millisecondsTimeout);
- if (ct.IsCancellationRequested)
- {
- throw new NormalEndException("取消自动任务");
- }
+ _delayManager.Sleep(millisecondsTimeout, ct);
}
public static async Task Delay(int millisecondsTimeout, CancellationToken ct)
@@ -160,26 +138,7 @@ public class TaskControl
throw new NormalEndException("取消自动任务");
}
- if (millisecondsTimeout <= 0)
- {
- return;
- }
-
- NewRetry.Do(() =>
- {
- if (ct is { IsCancellationRequested: true })
- {
- throw new NormalEndException("取消自动任务");
- }
-
- TrySuspend();
- CheckAndActivateGameWindow();
- }, TimeSpan.FromSeconds(1), 100);
- await Task.Delay(millisecondsTimeout, ct);
- if (ct is { IsCancellationRequested: true })
- {
- throw new NormalEndException("取消自动任务");
- }
+ await _delayManager.DelayAsync(millisecondsTimeout, ct);
}
public static Mat CaptureGameImage(IGameCapture? gameCapture)