Blazor:如何实现无UI线程阻塞的对象序列化?
解决方案
1. 分块处理+主动让出UI线程
在序列化循环中,每处理一定数量的对象就主动调用await Task.Yield(),给UI线程留出处理其他任务的间隙,把长时间阻塞拆分成多个短停顿:
var reader = new Utf8JsonAsyncStreamReader(await responseMessage.Content.ReadAsStreamAsync()); var list = new List<SimplifiedPlacemark>(); var simplifiedMark = new SimplifiedPlacemark(); int processedCount = 0; while (await reader.ReadAsync()) { JsonTokenType tokenType = reader.TokenType; switch (tokenType) { // 保留你原有的所有case逻辑 case JsonTokenType.EndObject: list.Add(simplifiedMark); processedCount++; // 每处理50个对象就让出一次UI线程 if (processedCount % 50 == 0) { await Task.Yield(); } break; } }
2. 后台线程序列化+分批更新UI
把序列化逻辑完全放到后台线程执行,不占用UI线程,同时分批将处理好的对象传回UI更新,避免一次性传递大量数据:
// UI线程初始化列表 var list = new List<SimplifiedPlacemark>(); var cancellationTokenSource = new CancellationTokenSource(); // 后台线程处理序列化 _ = Task.Run(async () => { var stream = await responseMessage.Content.ReadAsStreamAsync(); var reader = new Utf8JsonAsyncStreamReader(stream); var simplifiedMark = new SimplifiedPlacemark(); var batch = new List<SimplifiedPlacemark>(); const int batchSize = 100; while (await reader.ReadAsync() && !cancellationTokenSource.Token.IsCancellationRequested) { JsonTokenType tokenType = reader.TokenType; switch (tokenType) { // 保留你原有的所有case逻辑 case JsonTokenType.EndObject: batch.Add(simplifiedMark); if (batch.Count >= batchSize) { // 回到UI线程更新列表(Blazor用InvokeAsync,WPF/UWP用Dispatcher.InvokeAsync) await InvokeAsync(() => { list.AddRange(batch); StateHasChanged(); // Blazor中触发UI刷新 }); batch.Clear(); await Task.Delay(10); // 给UI留一点缓冲时间 } break; } } // 处理剩余的最后一批对象 if (batch.Count > 0) { await InvokeAsync(() => { list.AddRange(batch); StateHasChanged(); }); } }, cancellationTokenSource.Token);
3. 优化WebWorker的对象传输逻辑
之前WebWorker导致UI阻塞,核心是一次性传递大量对象的结构化克隆开销太大。改成WebWorker分批序列化并发送对象:
- WebWorker端:每序列化100个对象就通过
postMessage发送一批,然后清空当前批次 - UI端:监听
message事件,每次收到批次就添加到List并刷新UI
这样能避免一次性传递海量数据带来的UI卡顿。
4. 改用原生流式序列化API
用System.Text.Json的DeserializeAsyncEnumerable原生流式API,它本身就是异步分块处理的,配合间隙让出UI线程,代码更简洁且性能更优:
var stream = await responseMessage.Content.ReadAsStreamAsync(); var jsonOptions = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; var placemarkEnumerable = JsonSerializer.DeserializeAsyncEnumerable<SimplifiedPlacemark>(stream, jsonOptions); var list = new List<SimplifiedPlacemark>(); int processedCount = 0; await foreach (var placemark in placemarkEnumerable) { if (placemark != null) { list.Add(placemark); processedCount++; if (processedCount % 50 == 0) { await Task.Yield(); StateHasChanged(); // Blazor中触发UI刷新 } } }
内容的提问来源于stack exchange,提问作者user2657943
相关产品推荐
相关产品推荐

