diff --git a/Assets/GameMain/Scripts/Runtime/BuiltinComponent/AsyncTask/AsyncTaskHelper.cs b/Assets/GameMain/Scripts/Runtime/BuiltinComponent/AsyncTask/AsyncTaskHelper.cs index 001196d..e699d11 100644 --- a/Assets/GameMain/Scripts/Runtime/BuiltinComponent/AsyncTask/AsyncTaskHelper.cs +++ b/Assets/GameMain/Scripts/Runtime/BuiltinComponent/AsyncTask/AsyncTaskHelper.cs @@ -17,13 +17,43 @@ namespace SepCore.AsyncTask /// 事件ID /// 事件过滤条件 /// 超时时间(秒),0表示不超时 + /// 取消令牌 /// 事件参数 - public static UniTask WaitEventAsync(int eventId, Func predicate = null, float timeout = 0f) where T : GameEventArgs + public static UniTask WaitEventAsync( + int eventId, + Func predicate = null, + float timeout = 0f, + CancellationToken cancellationToken = default) where T : GameEventArgs { var tcs = new UniTaskCompletionSource(); var eventComponent = global::GameEntry.Event; + var timeoutCancellationTokenSource = timeout > 0f ? new CancellationTokenSource() : null; + CancellationTokenRegistration cancellationRegistration = default; + int isCompleted = 0; EventHandler handler = null; + void Cleanup(bool disposeCancellationRegistration) + { + eventComponent.Unsubscribe(eventId, handler); + timeoutCancellationTokenSource?.Cancel(); + timeoutCancellationTokenSource?.Dispose(); + if (disposeCancellationRegistration) + { + cancellationRegistration.Dispose(); + } + } + + bool TryComplete(bool disposeCancellationRegistration = true) + { + if (Interlocked.Exchange(ref isCompleted, 1) != 0) + { + return false; + } + + Cleanup(disposeCancellationRegistration); + return true; + } + handler = (_, e) => { var args = e as T; @@ -31,23 +61,35 @@ namespace SepCore.AsyncTask if (predicate != null && !predicate(args)) return; - eventComponent.Unsubscribe(eventId, handler); - tcs.TrySetResult(args); + if (TryComplete()) + { + tcs.TrySetResult(args); + } }; eventComponent.Subscribe(eventId, handler); - // 超时处理 - if (timeout > 0f) + if (cancellationToken.CanBeCanceled) { - UniTask.Delay(TimeSpan.FromSeconds(timeout), cancellationToken: CancellationToken.None) - .ContinueWith(() => + cancellationRegistration = cancellationToken.Register(() => + { + if (TryComplete(false)) { - if (tcs.TrySetException(new TimeoutException($"等待事件超时: {timeout}秒"))) - { - eventComponent.Unsubscribe(eventId, handler); - } - }); + tcs.TrySetCanceled(cancellationToken); + } + }); + } + + // 超时处理 + if (timeoutCancellationTokenSource != null) + { + WaitTimeoutAsync(timeout, timeoutCancellationTokenSource.Token, () => + { + if (TryComplete()) + { + tcs.TrySetException(new TimeoutException($"等待事件超时: {timeout}秒")); + } + }).Forget(); } return tcs.Task; @@ -63,22 +105,49 @@ namespace SepCore.AsyncTask /// 成功事件过滤条件 /// 失败事件过滤条件 /// 超时时间(秒),0表示不超时 + /// 取消令牌 /// 成功事件参数 public static UniTask WaitSuccessOrFailureAsync( int successEventId, int failureEventId, Func successPredicate = null, Func failurePredicate = null, - float timeout = 0f) + float timeout = 0f, + CancellationToken cancellationToken = default) where TSuccess : GameEventArgs where TFailure : GameEventArgs { var tcs = new UniTaskCompletionSource(); var eventComponent = global::GameEntry.Event; + var timeoutCancellationTokenSource = timeout > 0f ? new CancellationTokenSource() : null; + CancellationTokenRegistration cancellationRegistration = default; + int isCompleted = 0; // 先声明两个 handler,再赋值 lambda,避免前向引用 EventHandler successHandler = null; EventHandler failureHandler = null; + void Cleanup(bool disposeCancellationRegistration) + { + eventComponent.Unsubscribe(successEventId, successHandler); + eventComponent.Unsubscribe(failureEventId, failureHandler); + timeoutCancellationTokenSource?.Cancel(); + timeoutCancellationTokenSource?.Dispose(); + if (disposeCancellationRegistration) + { + cancellationRegistration.Dispose(); + } + } + + bool TryComplete(bool disposeCancellationRegistration = true) + { + if (Interlocked.Exchange(ref isCompleted, 1) != 0) + { + return false; + } + + Cleanup(disposeCancellationRegistration); + return true; + } successHandler = (_, e) => { @@ -87,9 +156,10 @@ namespace SepCore.AsyncTask if (successPredicate != null && !successPredicate(args)) return; - eventComponent.Unsubscribe(successEventId, successHandler); - eventComponent.Unsubscribe(failureEventId, failureHandler); - tcs.TrySetResult(args); + if (TryComplete()) + { + tcs.TrySetResult(args); + } }; failureHandler = (_, e) => @@ -99,29 +169,53 @@ namespace SepCore.AsyncTask if (failurePredicate != null && !failurePredicate(args)) return; - eventComponent.Unsubscribe(successEventId, successHandler); - eventComponent.Unsubscribe(failureEventId, failureHandler); - tcs.TrySetException(new Exception($"操作失败: {args}")); + if (TryComplete()) + { + tcs.TrySetException(new Exception($"操作失败: {args}")); + } }; eventComponent.Subscribe(successEventId, successHandler); eventComponent.Subscribe(failureEventId, failureHandler); - // 超时处理 - if (timeout > 0f) + if (cancellationToken.CanBeCanceled) { - UniTask.Delay(TimeSpan.FromSeconds(timeout), cancellationToken: CancellationToken.None) - .ContinueWith(() => + cancellationRegistration = cancellationToken.Register(() => + { + if (TryComplete(false)) { - if (tcs.TrySetException(new TimeoutException($"等待事件超时: {timeout}秒"))) - { - eventComponent.Unsubscribe(successEventId, successHandler); - eventComponent.Unsubscribe(failureEventId, failureHandler); - } - }); + tcs.TrySetCanceled(cancellationToken); + } + }); + } + + // 超时处理 + if (timeoutCancellationTokenSource != null) + { + WaitTimeoutAsync(timeout, timeoutCancellationTokenSource.Token, () => + { + if (TryComplete()) + { + tcs.TrySetException(new TimeoutException($"等待事件超时: {timeout}秒")); + } + }).Forget(); } return tcs.Task; } + + private static async UniTaskVoid WaitTimeoutAsync(float timeout, CancellationToken cancellationToken, Action onTimeout) + { + try + { + await UniTask.Delay(TimeSpan.FromSeconds(timeout), cancellationToken: cancellationToken); + } + catch (OperationCanceledException) + { + return; + } + + onTimeout?.Invoke(); + } } }