Skip to content

fix(Button): add Task yield prevent block thread#7303

Merged
ArgoZhang merged 2 commits intomainfrom
fix-button
Dec 12, 2025
Merged

fix(Button): add Task yield prevent block thread#7303
ArgoZhang merged 2 commits intomainfrom
fix-button

Conversation

@ArgoZhang
Copy link
Copy Markdown
Member

@ArgoZhang ArgoZhang commented Dec 12, 2025

Link issues

fixes #7302

Summary By Copilot

Regression?

  • Yes
  • No

Risk

  • High
  • Medium
  • Low

Verification

  • Manual (required)
  • Automated

Packaging changes reviewed?

  • Yes
  • No
  • N/A

☑️ Self Check before Merge

⚠️ Please check all items below before review. ⚠️

  • Doc is updated/provided or not needed
  • Demo is updated/provided or not needed
  • Merge the latest code from the main branch

Summary by Sourcery

Bug Fixes:

  • Ensure async button clicks yield control to the UI thread after setting loading and disabled states to avoid thread blocking or UI freezes.

Copilot AI review requested due to automatic review settings December 12, 2025 02:33
@bb-auto bb-auto Bot added the bug Something isn't working label Dec 12, 2025
@bb-auto bb-auto Bot added this to the v10.1.0 milestone Dec 12, 2025
@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai Bot commented Dec 12, 2025

Reviewer's guide (collapsed on small PRs)

Reviewer's Guide

Ensures the async button click handler yields control back to the UI thread before executing the click handler to avoid blocking the UI when using async loading/disabled state.

Sequence diagram for async button click with Task_Yield

sequenceDiagram
    actor User
    participant UIThread
    participant Button
    participant ClickHandler

    User->>UIThread: Clicks button
    UIThread->>Button: OnClickButton()
    Button->>Button: IsAsyncLoading = true
    Button->>Button: IsDisabled = true
    Button->>UIThread: await Task.Yield()
    UIThread-->>User: UI re-renders with loading/disabled
    UIThread->>ClickHandler: HandlerClick()
    ClickHandler-->>UIThread: Complete
    UIThread->>Button: Update IsAsyncLoading/IsDisabled if needed
    UIThread-->>User: UI reflects final state
Loading

Class diagram for Button component OnClickButton change

classDiagram
    class Button {
        bool IsAsyncLoading
        bool IsDisabled
        Task OnClickButton()
        Task HandlerClick()
    }

    Button : OnClickButton() sets IsAsyncLoading = true
    Button : OnClickButton() sets IsDisabled = true
    Button : OnClickButton() awaits Task.Yield()
    Button : OnClickButton() awaits HandlerClick()
Loading

File-Level Changes

Change Details Files
Prevent UI thread blocking when an async button click starts by yielding once before executing the click handler.
  • When entering the async loading state, immediately await Task.Yield() so the UI can render IsAsyncLoading/IsDisabled state before HandlerClick runs
  • Left existing HandlerClick invocation and async loading/disabled logic unchanged
src/BootstrapBlazor/Components/Button/Button.razor.cs

Assessment against linked issues

Issue Objective Addressed Explanation
#7302 Ensure that when a Button triggers an asynchronous task, the button is correctly re-rendered after the task completes and its disabled/loading state is restored.

Possibly linked issues


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

sourcery-ai[bot]
sourcery-ai Bot previously approved these changes Dec 12, 2025
Copy link
Copy Markdown
Contributor

@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 there - I've reviewed your changes - here's some feedback:

  • Consider replacing await Task.Yield(); with a more explicit UI update mechanism (e.g., await InvokeAsync(StateHasChanged);) or at least adding a brief comment explaining why yielding is required here, so the intent is clear and future maintainers don’t inadvertently remove or alter this line.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Consider replacing `await Task.Yield();` with a more explicit UI update mechanism (e.g., `await InvokeAsync(StateHasChanged);`) or at least adding a brief comment explaining why yielding is required here, so the intent is clear and future maintainers don’t inadvertently remove or alter this line.

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.

@ArgoZhang ArgoZhang merged commit 8dd3e1d into main Dec 12, 2025
4 checks passed
@ArgoZhang ArgoZhang deleted the fix-button branch December 12, 2025 02:34
@codecov
Copy link
Copy Markdown

codecov Bot commented Dec 12, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (dbf866c) to head (f63b6c3).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff            @@
##              main     #7303   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files          745       745           
  Lines        32629     32630    +1     
  Branches      4520      4521    +1     
=========================================
+ Hits         32629     32630    +1     
Flag Coverage Δ
BB 100.00% <100.00%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR addresses issue #7302 by preventing the button component from blocking the UI thread during async operations. The fix adds await Task.Yield() after setting the button's loading and disabled states to ensure the UI thread is not blocked before executing the click handler.

Key Changes

  • Added await Task.Yield() in the OnClickButton method to yield control back to the UI thread after setting async loading states
  • Removed BOM (Byte Order Mark) character from the beginning of the file

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@@ -46,6 +46,7 @@ protected virtual async Task OnClickButton()
{
IsAsyncLoading = true;
IsDisabled = true;
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

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

After setting IsAsyncLoading and IsDisabled to true, StateHasChanged() should be called to immediately update the UI before yielding control. This ensures the loading indicator and disabled state are rendered before the async operation continues. The PopConfirmButton component follows this pattern by calling StateHasChanged() immediately after setting these properties (see PopConfirmButton.razor.cs lines 100-101).

Suggested change
IsDisabled = true;
IsDisabled = true;
StateHasChanged();

Copilot uses AI. Check for mistakes.
{
IsAsyncLoading = true;
IsDisabled = true;
await Task.Yield();
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

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

Consider applying this same pattern to other button components (LinkButton, ToggleButton) which also set IsAsyncLoading and IsDisabled when IsAsync is true but don't call Task.Yield(). This inconsistency means only the base Button component will prevent blocking the UI thread, while other async button types may still experience the original issue.

Copilot uses AI. Check for mistakes.
{
IsAsyncLoading = true;
IsDisabled = true;
await Task.Yield();
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

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

The addition of Task.Yield() should have test coverage to verify that it properly prevents UI thread blocking. Consider adding a test case in ButtonTest.cs that verifies the button's loading state is rendered before the async operation completes, ensuring the Task.Yield() behavior is working as intended.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug(Button): button not render after task complete

2 participants