使用 SignalR 向前端推送图像
我的早期方案;
后端不断读取图像,并刷新缓存,前端不断轮询缓存来更新图像;这样的结果就是图像展示非常不连贯
public class VideoService { const string VideoFilePath = "D:\\Users\\xx\\Desktop\\"; /// <summary> /// 运行中 /// </summary> public bool IsRunning { get; private set; } = false; object locker = new(); CancellationTokenSource cts = new(); Memory<byte>? _memoryCache; /// <summary> /// 图片base64; /// </summary> public string ImageBase64 => _memoryCache == null ? "" : Convert.ToBase64String(_memoryCache.Value.Span); /// <summary> /// 启动 /// </summary> public void Startup() { if (IsRunning) return; Monitor.Enter(locker); Task.Factory.StartNew(() => { IsRunning = true; int index = 1; while (!cts.Token.IsCancellationRequested) { // 长时间运行的逻辑 string filePath = Path.Combine(VideoFilePath, $"{index}.jpg"); if (index >= 2) index = 1; else index++; if (File.Exists(filePath)) { var bytesRead = File.ReadAllBytes(filePath); _memoryCache = new Memory<byte>(bytesRead, 0, bytesRead.Length); } } }, cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default).ContinueWith(t => { IsRunning = false; cts = new(); Monitor.Exit(locker); }); } /// <summary> /// 关闭 /// </summary> public void Shutdown() { if (!IsRunning) return; cts.Cancel(); } }
改造后使用 SignalR 向前端推送;
依赖 Microsoft.AspNetCore.SignalR 和 Microsoft.AspNetCore.SignalR.Client ; 在 nuget 下载
1 创建一个 Hub
/// <summary> /// 主要用来映射连接端点 /// </summary> public class VideoStreamHub : Hub { // 可以在这里实现客户端连接管理 }
2 启用 SignalR 服务,并注册端点

3 改造 service
public class VideoService { const string VideoFilePath = "D:\\Users\\xx\\Desktop\\"; private readonly IHubContext<VideoStreamHub> _hubContext; public VideoService(IHubContext<VideoStreamHub> hubContext) { _hubContext = hubContext; } /// <summary> /// 运行中 /// </summary> public bool IsRunning { get; private set; } = false; object locker = new(); CancellationTokenSource cts = new(); /// <summary> /// 启动 /// </summary> public void Startup() { if (IsRunning) return; Monitor.Enter(locker); cts = new(); Task.Factory.StartNew(async () => { IsRunning = true; int index = 1; while (!cts.Token.IsCancellationRequested) { // 长时间运行的逻辑 string filePath = Path.Combine(VideoFilePath, $"{index}.jpg"); if (index >= 2) index = 1; else index++; if (File.Exists(filePath)) { var bytesRead = File.ReadAllBytes(filePath); _ = _hubContext.Clients.All.SendAsync("ReceiveFrame", Convert.ToBase64String(bytesRead), cts.Token) // 非阻塞发送,避免单个客户端拖慢整体速度 .ContinueWith(t => { if (t.IsFaulted) { // 处理发送失败,通常是客户端已断开 // 可以记录日志或进行重连处理 } }); } await Task.Delay(10, cts.Token); } }, cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default) .Unwrap() .ContinueWith(t => { IsRunning = false; Monitor.Exit(locker); }); } /// <summary> /// 关闭 /// </summary> public void Shutdown() { if (!IsRunning) return; cts.Cancel(); } }
4 前端注册接收方法
@page "/" @using BlazorAppVideo.Services @using Microsoft.AspNetCore.SignalR.Client @inject VideoService _VideoService @implements IDisposable @inject NavigationManager NavManager <PageTitle>Home</PageTitle> <img src="@currentFrame" alt="Blazor logo" /> @code { string currentFrame { get; set; } = string.Empty; private HubConnection? hubConnection; CancellationTokenSource source = new(); protected override async Task OnAfterRenderAsync(bool firstRender) { await base.OnAfterRenderAsync(firstRender); if (firstRender) { // 启动视频服务 _VideoService.Startup(); // 创建SignalR连接 hubConnection = new HubConnectionBuilder() .WithUrl(NavManager.ToAbsoluteUri("/videoStreamHub")) .Build(); // 注册接收帧的处理方法 hubConnection.On<string>("ReceiveFrame", (frame) => { currentFrame = $"data:image/jpeg;base64,{frame}"; InvokeAsync(StateHasChanged); // 接收到新帧后更新UI }); await hubConnection.StartAsync(); } } public void Dispose() { hubConnection?.DisposeAsync(); } }
改造后图像非常连贯,可用于视觉项目,从服务端推送采集的图像到前端

浙公网安备 33010602011771号