Blazor中ProtectedLocalStorage的SetAsync方法await时无限挂起问题
问题根源:异步操作的同步阻塞导致死锁
你的代码出现挂起的核心原因是同步代码阻塞异步操作引发的死锁,具体逻辑链如下:
- 直接触发点:
GetValue属性的getter中使用了LocalStorage.GetAsync<int>("counter").Result——这是典型的错误写法,在Blazor Server的同步上下文(SynchronizationContext)中,.Result会阻塞当前线程等待异步任务完成,而GetAsync本身依赖该上下文执行,最终形成循环阻塞。 - 表象误导:点击按钮执行
TestLocalStorage时,await LocalStorage.SetAsync(...)执行完成后,你调用InvokeAsync(StateHasChanged)触发组件重新渲染。此时组件会重新计算@GetValue的值,进入getter的同步阻塞逻辑,占用了Blazor的同步上下文,导致后续异步流程无法继续,看起来就像是SetAsync挂起了。
修复方案:避免同步阻塞异步操作
方案1:使用本地字段存储值,在生命周期/异步方法中加载数据
把异步获取本地存储的逻辑放到组件初始化或专用异步方法中,用本地字段存储结果,不在属性getter中执行异步操作:
@page "/" @using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage <PageTitle>Index</PageTitle> <h1>Hello, world!</h1> Welcome to your new app. <SurveyPrompt Title="How is Blazor working for you?"/> <button @onclick="TestLocalStorage">@displayCounter</button> @code { [Inject] ProtectedLocalStorage LocalStorage { get; set; } private int counter; private int displayCounter; protected override async Task OnInitializedAsync() { // 组件初始化时从本地存储加载值 var storedCounter = await LocalStorage.GetAsync<int>("counter"); if (storedCounter.Success) { counter = storedCounter.Value; displayCounter = counter; } } private async Task TestLocalStorage() { counter++; await LocalStorage.SetAsync("counter", counter); displayCounter = counter; // 属性更新后Blazor会自动触发渲染,无需手动调用StateHasChanged } }
方案2:使用异步绑定表达式(直接绑定异步方法)
如果需要直接在UI中绑定异步数据,可以使用@await表达式调用异步方法:
@page "/" @using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage <PageTitle>Index</PageTitle> <h1>Hello, world!</h1> Welcome to your new app. <SurveyPrompt Title="How is Blazor working for you?"/> <button @onclick="TestLocalStorage">@(await GetCounterValue())</button> @code { [Inject] ProtectedLocalStorage LocalStorage { get; set; } private int counter; private async Task<int> GetCounterValue() { if (counter == 0) return 0; var result = await LocalStorage.GetAsync<int>("counter"); return result.Success ? result.Value : 0; } private async Task TestLocalStorage() { counter++; await LocalStorage.SetAsync("counter", counter); } }
你提到的对组件渲染生命周期的误解是关键——ProtectedLocalStorage的方法本身不会触发组件渲染,但InvokeAsync(StateHasChanged)会触发重新渲染,此时同步阻塞的属性getter占用上下文导致死锁。只要避免在同步代码中阻塞异步操作,改用异步方式处理数据加载和绑定,就能解决问题。
内容的提问来源于stack exchange,提问作者Paul Carlton
相关产品推荐
相关产品推荐

