Blazor Server日志页面加载延迟与文件读取性能优化咨询
Blazor Server大日志文件加载优化方案
问题场景
现有Blazor Server应用,其中一个Razor页面展示服务器上2MB、约3万行的大日志文件,点击按钮可刷新内容。存在两个问题:
- 首次点击刷新按钮需4-5秒才开始显示内容;
- 离开页面后再次返回,页面需等待4-5秒才打开,推测是重新加载/渲染所有日志行导致。
需求
- 实现页面立即打开并显示加载动画,后台加载日志,完成后再展示内容;
- 确认2MB日志读取耗时4-5秒是否正常,提供代码优化方案缩短耗时。
用户现有代码
Razor页面代码
<body> <table class="page_header"> <tr class="tr_button_bar"> <td class="td_button_bar" align="left"> <button class="button_1" @onclick="Read_Error_Log"><img class="button_image" src="images/read_data.svg"><br>Error Log</button> </td> </tr> </table> <br> <style> .preformatted { white-space: pre; max-width:1600px; margin: 0; line-height: 1; } </style> @if (TagService.Loading_Main_Log == true) { <div class="spinner"></div> } <table class="log_file_table"> @for (int f = 0; f < TagService.Error_log.Count; f++) { <tr style="max-height:20px !important"> <td class=".log_file"><pre class="preformatted">@TagService.Error_log[f]</pre></td> </tr> } </table> </body>
Razor.cs代码
public partial class Logs { public async Task Read_Error_Log() { TagService.Loading_Main_Log = true; await Task.Delay(1); try { TagService.Error_log.Clear(); Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); var lines = File.ReadLines(CommonClass.error_path, Encoding.GetEncoding("Windows-1254")); var line_trimmed = ""; foreach (var line in lines) { TagService.Error_log.Add(line); } } catch (Exception e) { using (StreamWriter sw = File.AppendText(CommonClass.error_path)) { sw.WriteLine("Read_Error_Log() Error " + e + " " + Convert.ToString(DateTime.Now)); } } TagService.Loading_Main_Log = false; } }
页面导航代码
<NavLink class="nav-link" href="logs" Match="NavLinkMatch.All"> <span class="oi oi-home" aria-hidden="true"></span> Error Log
解决方案
一、实现页面立即打开+加载动画
核心问题:原代码一次性渲染3万行DOM,Blazor Server需在服务端生成所有DOM再发送到客户端,导致页面打开阻塞。优化方案如下:
1. 改用虚拟滚动组件减少初始DOM渲染
使用Blazor内置的Virtualize组件,仅渲染当前可视区域的日志行,初始渲染行数从3万降到几十行,页面瞬间打开。
2. 页面初始化异步加载日志
在页面初始化时触发日志加载,同时显示加载动画,避免白屏等待。
修改后的Razor页面代码:
@page "/logs" @inject TagService TagService <table class="page_header"> <tr class="tr_button_bar"> <td class="td_button_bar" align="left"> <button class="button_1" @onclick="Read_Error_Log"> <img class="button_image" src="images/read_data.svg"><br>Error Log </button> </td> </tr> </table> <br> <style> .preformatted { white-space: pre; max-width:1600px; margin: 0; line-height: 1; } .spinner { border: 4px solid rgba(0,0,0,0.1); width: 36px; height: 36px; border-radius: 50%; border-left-color: #09f; animation: spin 1s linear infinite; margin: 20px auto; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } </style> @if (TagService.Loading_Main_Log) { <div class="spinner"></div> } <!-- 虚拟滚动组件,仅渲染可视区域行 --> <Virtualize Items="@TagService.Error_log" Context="line" ItemSize="20"> <tr> <td class="log_file"><pre class="preformatted">@line</pre></td> </tr> </Virtualize>
二、优化日志读取速度
耗时判断:2MB日志读取4-5秒完全不正常,正常耗时应在100ms以内。原代码的问题点:
- 每次读取重复注册编码提供者,冗余操作;
- 使用同步
File.ReadLines阻塞UI线程; - 逐行添加到共享列表,频繁触发UI更新(若使用
ObservableCollection)。
优化后的Razor.cs代码:
public partial class Logs { [Inject] public TagService TagService { get; set; } // 页面初始化时自动加载日志 protected override async Task OnInitializedAsync() { await Read_Error_Log(); } public async Task Read_Error_Log() { TagService.Loading_Main_Log = true; StateHasChanged(); // 立即通知UI显示加载动画 try { // 本地缓存所有行,避免频繁更新共享列表 var localLines = new List<string>(); // 异步读取文件,不阻塞UI线程 await using var stream = new FileStream( CommonClass.error_path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite // 允许其他进程写入文件时读取 ); using var reader = new StreamReader(stream, Encoding.GetEncoding("Windows-1254")); string line; while ((line = await reader.ReadLineAsync()) != null) { localLines.Add(line); } // 批量更新共享列表,减少UI更新次数 TagService.Error_log.Clear(); TagService.Error_log.AddRange(localLines); } catch (Exception e) { await using var sw = File.AppendText(CommonClass.error_path); await sw.WriteLineAsync($"Read_Error_Log() Error {e} {DateTime.Now}"); } finally { TagService.Loading_Main_Log = false; StateHasChanged(); // 通知UI隐藏加载动画 } } }
额外优化:
- 编码注册移到启动时:在
Program.cs中添加Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);,仅执行一次; - 缓存日志内容:若日志不是实时更新,可添加缓存逻辑(如设置5分钟有效期),避免重复读取;
- 线程安全:若
TagService是单例,需确保Error_log的操作线程安全(可加锁或使用线程安全集合)。
内容的提问来源于stack exchange,提问作者Mdarende
相关产品推荐
相关产品推荐

