Skip to content

feat(state): 实现异步状态基类的同步方法限制#25

Merged
GeWuYou merged 2 commits into
mainfrom
feat/async-state-management-improvement
Feb 15, 2026
Merged

feat(state): 实现异步状态基类的同步方法限制#25
GeWuYou merged 2 commits into
mainfrom
feat/async-state-management-improvement

Conversation

@GeWuYou
Copy link
Copy Markdown
Owner

@GeWuYou GeWuYou commented Feb 15, 2026

  • 为 AsyncContextAwareStateBase 添加同步方法异常抛出机制
  • 禁止在异步状态中使用 OnEnter、OnExit 和 CanTransitionTo 同步方法
  • 提供清晰的错误提示引导使用对应的异步方法
  • 修复 StateMachine 中的状态转换逻辑确保线程安全
  • 更新 IAsyncState 接口继承 IState 接口统一状态管理

Summary by Sourcery

Restrict synchronous APIs on async states and improve async state machine safety and consistency.

New Features:

  • Add explicit NotSupportedException-throwing implementations of synchronous lifecycle and transition methods on async state base types to guide callers toward async equivalents.
  • Update the async state interface to inherit from the base state interface for unified state management.

Bug Fixes:

  • Fix a race condition in the async state machine transition logic by taking a snapshot of the current state under lock before performing async validation and callbacks.

- 为 AsyncContextAwareStateBase 添加同步方法异常抛出机制
- 禁止在异步状态中使用 OnEnter、OnExit 和 CanTransitionTo 同步方法
- 提供清晰的错误提示引导使用对应的异步方法
- 修复 StateMachine 中的状态转换逻辑确保线程安全
- 更新 IAsyncState 接口继承 IState 接口统一状态管理
@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented Feb 15, 2026

Reviewer's Guide

Enforces that async states cannot use sync lifecycle/transition methods by throwing clear NotSupportedException messages, aligns IAsyncState with IState for unified state management, and fixes StateMachine async transition logic to use a thread-safe snapshot of the current state during asynchronous checks and callbacks.

Sequence diagram for thread-safe async state transition using snapshot

sequenceDiagram
    actor Caller
    participant StateMachine
    participant CurrentState as IState_currentSnapshot
    participant TargetState as IState_target

    Caller->>StateMachine: ChangeToAsync~T~()
    activate StateMachine
    StateMachine->>StateMachine: lock(_lock)
    StateMachine->>StateMachine: resolve TargetState from States
    StateMachine->>StateMachine: currentSnapshot = Current
    StateMachine->>StateMachine: release lock(_lock)

    alt currentSnapshot is not null
        StateMachine->>CurrentState: CanTransitionToAsync(TargetState)
        activate CurrentState
        CurrentState-->>StateMachine: canTransition
        deactivate CurrentState

        alt canTransition is false
            StateMachine->>CurrentState: OnTransitionRejectedAsync(TargetState)
            StateMachine-->>Caller: false
        else canTransition is true
            StateMachine->>StateMachine: perform transition to TargetState
            StateMachine-->>Caller: true
        end
    else currentSnapshot is null
        StateMachine->>StateMachine: perform transition to TargetState
        StateMachine-->>Caller: true
    end
    deactivate StateMachine
Loading

Class diagram for async state restrictions and interface alignment

classDiagram
    class IState {
        <<interface>>
        +void OnEnter(IState from)
        +void OnExit(IState to)
        +bool CanTransitionTo(IState target)
    }

    class IAsyncState {
        <<interface>>
        +Task OnEnterAsync(IState from)
        +Task OnExitAsync(IState to)
        +Task~bool~ CanTransitionToAsync(IState target)
    }

    class AsyncContextAwareStateBase {
        +Task OnEnterAsync(IState from)
        +Task OnExitAsync(IState to)
        +Task~bool~ CanTransitionToAsync(IState target)
        +void OnEnter(IState from)
        +void OnExit(IState to)
        +bool CanTransitionTo(IState target)
    }

    IAsyncState --|> IState
    AsyncContextAwareStateBase ..|> IAsyncState

    class StateMachine {
        -Dictionary~Type,IState~ States
        -IState Current
        -object _lock
        +bool ChangeTo~T~()
        +Task~bool~ ChangeToAsync~T~()
        +Task~bool~ CanTransitionToAsync(IState fromState, IState toState)
        +Task OnTransitionRejectedAsync(IState fromState, IState toState)
    }

    StateMachine --> IState
    StateMachine --> IAsyncState
    AsyncContextAwareStateBase --> StateMachine
Loading

File-Level Changes

Change Details Files
Disallow use of synchronous lifecycle and transition methods on async state base types with explicit exceptions and guidance.
  • Override OnEnter to always throw NotSupportedException with a message directing callers to OnEnterAsync
  • Override OnExit to always throw NotSupportedException with a message directing callers to OnExitAsync
  • Override CanTransitionTo to always throw NotSupportedException with a message directing callers to CanTransitionToAsync
GFramework.Core/state/AsyncContextAwareStateBase.cs
Make async state interface inherit from the base state interface to unify state management.
  • Update IAsyncState declaration to extend IState so async states are recognized as regular states by the state machine and related infrastructure
GFramework.Core.Abstractions/state/IAsyncState.cs
Harden async state transitions for thread safety by using a captured snapshot of the current state outside the lock.
  • Capture Current into a local variable currentSnapshot inside the lock before async work begins
  • Use currentSnapshot instead of Current for CanTransitionToAsync and OnTransitionRejectedAsync calls to avoid races if Current changes during await
GFramework.Core/state/StateMachine.cs

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue, and left some high level feedback:

  • In the AsyncContextAwareStateBase exception messages, consider using nameof(OnEnterAsync), nameof(OnExitAsync), and nameof(CanTransitionToAsync) instead of hard-coded method names so refactors don’t silently desync the guidance text.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In the `AsyncContextAwareStateBase` exception messages, consider using `nameof(OnEnterAsync)`, `nameof(OnExitAsync)`, and `nameof(CanTransitionToAsync)` instead of hard-coded method names so refactors don’t silently desync the guidance text.

## Individual Comments

### Comment 1
<location> `GFramework.Core/state/AsyncContextAwareStateBase.cs:69-78` </location>
<code_context>
+    public virtual void OnEnter(IState? from)
</code_context>

<issue_to_address>
**suggestion:** Consider marking the sync members as obsolete or explicit interface implementations to discourage use rather than only throwing at runtime.

Since these methods only exist for interface compatibility and just throw, misuse is only detected at runtime. To surface problems earlier, consider adding `[Obsolete("This is an async state. Use the async methods instead.", error: true)]` or making them explicit `IState` implementations so they’re less discoverable on async states, while still satisfying the interface contract.

Suggested implementation:

```csharp
    /// <summary>
    ///     同步进入状态(不推荐使用)
    ///     异步状态应该使用 OnEnterAsync 方法
    /// </summary>
    /// <exception cref="NotSupportedException">异步状态不支持同步操作</exception>
    [Obsolete("This is an async state. Use the async methods instead.", error: true)]
    public virtual void OnEnter(IState? from)
    {
        throw new NotSupportedException(
            $"This is an async state ({GetType().Name}). Use OnEnterAsync instead of OnEnter.");
    }

```

1. Apply the same `[Obsolete("This is an async state. Use the async methods instead.", error: true)]` attribute to the synchronous `OnExit` method and any other synchronous `IState` members that only exist to throw.
2. Ensure the file includes `using System;` at the top if it does not already, so the `Obsolete` attribute is available.
3. If you prefer to make these sync methods explicit interface implementations instead (or in addition), change their signatures from `public virtual void OnEnter(IState? from)` to an explicit form like `void IState.OnEnter(IState? from)` (and similarly for other members), and consider whether they still need to be `virtual` or overridden in derived async states.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread GFramework.Core/state/AsyncContextAwareStateBase.cs Outdated
- 更新类注释说明IAsyncState继承自IState接口
- 添加SetContext和GetContext方法用于架构上下文管理
- 实现Destroy方法用于状态销毁和资源释放
- 显式实现IState同步方法并标记为已弃用
- 使用Obsolete特性标注同步方法并提示使用异步版本
- 恢复IAsyncState异步方法的正常实现
- 添加上下文未设置时的操作异常处理
@GeWuYou GeWuYou merged commit 487f55f into main Feb 15, 2026
6 checks passed
@GeWuYou GeWuYou deleted the feat/async-state-management-improvement branch February 15, 2026 13:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant