Blazor WebAssembly组件Dispose时img的HTTP流请求未中断问题求助
我有一个Blazor WebAssembly托管模式应用,包含如下组件:
liveStreaming.razor
@if (_isStreaming) { <img src="@_streamUrl"> } else { // show loading circle }
liveStreaming.razor.cs
using Microsoft.AspNetCore.Components; using System; using System.Threading.Tasks; using System.Timers; public partial class LiveStreaming: ComponentBase, IDisposable { private bool _isStreaming; private string _streamUrl; private string _placeholderImgUrl; private Timer _checkConnectionTimer; protected override async Task OnInitializedAsync() { _isStreaming = false; _placeholderImgUrl = "emptyImage.jpg"; _checkConnectionTimer = new Timer(); _checkConnectionTimer.Interval = 6000; _checkConnectionTimer.Elapsed += CheckConnection; _checkConnectionTimer.Start(); // [...] } private async void CheckConnection(object sender, ElapsedEventArgs e) { _checkConnectionTimer.Stop(); if (IsConnectionEstablished()) { _isStreaming = true; _streamUrl = "http://192.168.0.2/axis-cgi/mjpg/video.cgi"; StateHasChanged(); } else { _isStreaming = false; StateHasChanged(); } _checkConnectionTimer.Start(); } public void Clean() { _checkConnectionTimer.Stop(); _streamUrl = _placeholderImgUrl; StateHasChanged(); } public async void Dispose() { if (_checkConnectionTimer != null) { _checkConnectionTimer.Dispose(); } } }
问题现象
未调用Close方法时,若导航到Blazor应用的其他组件,即便Dispose方法已被触发,img标签发起的流请求也不会中断,可通过带宽占用观测到该问题。此外,若重新回到该页面,假设流带宽为3Mb/s,当前带宽占用会再叠加3Mb/s。每次离开再进入该页面都会出现该问题,就像HTTP流请求从未被中断,每次都会新建请求而旧请求始终保留。
为规避该问题,添加了上述Clean()方法,还搭建了一套复杂的组件切换机制:每次收到导航到其他组件的请求时,该机制会先调用当前加载组件的Clean()方法,再执行_navigationManager.NavigateTo("OtherComponentName"),即Clean()始终在Dispose()方法之前被调用。
对该方案并不满意,因为需要为理应默认支持的功能额外实现复杂机制。请问有没有更优的实现方式?
已验证的测试结果
- 将Clean()中的代码移到Dispose()中无任何效果,即便在StateHasChanged()后调用
Task.Delay(1)也无效。推测是因为Dispose方法被调用后,组件就不再触发渲染了。 - 将Clean()中的代码修改为:
也无任何效果。看起来必须修改img的src才能强制终止HTTP流请求。_checkConnectionTimer.Stop(); _isStreaming = false; StateHasChanged();
补充发现
可以通过定义JavaScript方法负责重置img的src属性,并在Dispose()方法中调用该JS方法,即可规避该问题,无需使用上述的特殊 workaround。
解决方案
你补充的JS调用方案是目前最简洁的合规实现,核心原因是Blazor组件的Dispose触发时,组件渲染树已经被标记为废弃,此时调用StateHasChanged不会同步更新真实DOM,因此无法通过C#端修改变量的方式终止流请求。
方案1:轻量JS实现(推荐)
不需要额外的全局组件切换拦截机制,所有逻辑内聚在组件内部:
- 给img元素添加元素引用,注入JS运行时:
@if (_isStreaming) { <img @ref="_streamImgRef" src="@_streamUrl"> } else { // show loading circle } @inject IJSRuntime JSRuntime - 后台代码声明引用变量,实现异步销毁逻辑:
不需要单独定义全局JS方法,侵入性极低。private ElementReference _streamImgRef; // 推荐用IAsyncDisposable替代IDisposable适配Blazor异步场景 public async ValueTask DisposeAsync() { _checkConnectionTimer?.Dispose(); if (_streamImgRef.Context != null) { // 直接修改DOM的src属性为空,触发浏览器主动终止流请求 await JSRuntime.InvokeVoidAsync("eval", "arguments[0].src = ''", _streamImgRef); } }
方案2:无JS实现
如果不想引入JS交互,可以通过监听导航事件提前执行清理:
[Inject] private NavigationManager NavManager { get; set; } protected override void OnInitialized() { NavManager.LocationChanged += OnLocationChanged; // 原有初始化逻辑 } private async void OnLocationChanged(object sender, LocationChangedEventArgs e) { // 导航触发时、组件销毁前先执行清理,修改src触发DOM更新 _checkConnectionTimer.Stop(); _streamUrl = _placeholderImgUrl; _isStreaming = false; StateHasChanged(); // 预留少量时间完成DOM更新 await Task.Delay(50); } public void Dispose() { NavManager.LocationChanged -= OnLocationChanged; _checkConnectionTimer?.Dispose(); }
内容的提问来源于stack exchange,提问作者Alessandro Martinelli

