使用 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 服务,并注册端点

image

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();
    }
}

 

 改造后图像非常连贯,可用于视觉项目,从服务端推送采集的图像到前端

 

posted @ 2025-09-27 20:47  cchong005  阅读(13)  评论(0)    收藏  举报