Skip to main content

Streaming com SignalR | WebSocket | SignalR | CSharp

  • Envio contínuo de dados entre servidor e cliente

O que é streaming no SignalR

Streaming no SignalR é o envio de vários itens ao longo do tempo dentro de uma única chamada lógica.

Em vez disso:

await Clients.Caller.SendAsync("Mensagem", item1);
await Clients.Caller.SendAsync("Mensagem", item2);
await Clients.Caller.SendAsync("Mensagem", item3);

Você faz isso:

await foreach (var item in stream)
{
// recebe item por item
}

O próprio material de SignalR destaca que além de chamadas únicas, o SignalR permite streaming de dados do servidor para o cliente e do cliente para o servidor .


Tipos de streaming

TipoDireçãoExemplo
Server streamingServidor → ClienteAPI manda progresso para o desktop
Client streamingCliente → ServidorDesktop envia vários registros para API
BidirecionalAmbosUm lado transmite e o outro responde

Quando usar

Use streaming quando você precisa enviar dados aos poucos:

  • progresso de processamento
  • logs em tempo real
  • leitura incremental de dados
  • status de execução
  • envio de vários itens sem abrir várias chamadas
  • monitoramento em tempo real
  • comandos longos com retorno parcial

Não use streaming para uma resposta simples. Para isso, use InvokeAsync ou SendAsync.


Exemplo 1: Server streaming com IAsyncEnumerable

Neste modelo, o cliente chama um método no Hub e o servidor vai retornando itens aos poucos.


DTO

public sealed class ProgressoComandoDto
{
public Guid RequestId { get; init; }
public int Percentual { get; init; }
public string Mensagem { get; init; } = string.Empty;
}

Hub

using System.Runtime.CompilerServices;
using Microsoft.AspNetCore.SignalR;

public sealed class ComandoHub : Hub
{
public async IAsyncEnumerable<ProgressoComandoDto> ExecutarComandoComStream(
string sql,
[EnumeratorCancellation] CancellationToken cancellationToken)
{
Guid requestId = Guid.NewGuid();

for (int i = 0; i <= 100; i += 10)
{
cancellationToken.ThrowIfCancellationRequested();

yield return new ProgressoComandoDto
{
RequestId = requestId,
Percentual = i,
Mensagem = $"Processando comando: {i}%"
};

await Task.Delay(500, cancellationToken);
}
}
}

Cliente .NET consumindo stream

using Microsoft.AspNetCore.SignalR.Client;

var connection = new HubConnectionBuilder()
.WithUrl("https://localhost:5001/hub/comandos")
.WithAutomaticReconnect()
.Build();

await connection.StartAsync();

var stream = connection.StreamAsync<ProgressoComandoDto>(
"ExecutarComandoComStream",
"SELECT * FROM PRODUTOS");

await foreach (var item in stream)
{
Console.WriteLine($"{item.Percentual}% - {item.Mensagem}");
}

Fluxo visual

Cliente chama:
ExecutarComandoComStream(sql)

Servidor responde:
10%
20%
30%
40%
...
100%

Exemplo 2: Server streaming usando ChannelReader

Esse modelo é útil quando você quer escrever no stream a partir de outra tarefa.


Hub

using System.Threading.Channels;
using Microsoft.AspNetCore.SignalR;

public sealed class ComandoHub : Hub
{
public ChannelReader<ProgressoComandoDto> ExecutarComandoComChannel(string sql)
{
var channel = Channel.CreateUnbounded<ProgressoComandoDto>();
Guid requestId = Guid.NewGuid();

_ = Task.Run(async () =>
{
try
{
for (int i = 0; i <= 100; i += 10)
{
await channel.Writer.WriteAsync(new ProgressoComandoDto
{
RequestId = requestId,
Percentual = i,
Mensagem = $"Executando: {i}%"
});

await Task.Delay(500);
}
}
catch (Exception ex)
{
channel.Writer.TryComplete(ex);
return;
}

channel.Writer.TryComplete();
});

return channel.Reader;
}
}

Cliente .NET

var stream = connection.StreamAsync<ProgressoComandoDto>(
"ExecutarComandoComChannel",
"SELECT * FROM CLIENTES");

await foreach (var item in stream)
{
Console.WriteLine($"{item.Percentual}% - {item.Mensagem}");
}

Diferença entre IAsyncEnumerable e ChannelReader

OpçãoQuando usar
IAsyncEnumerable<T>fluxo simples, linear, fácil de entender
ChannelReader<T>fluxo produzido por tarefa, fila interna ou eventos externos

Para a maioria dos casos, comece com IAsyncEnumerable<T>.


Exemplo 3: Client streaming

Aqui o cliente envia vários itens para o servidor.


DTO

public sealed class RegistroClienteDto
{
public Guid RequestId { get; init; }
public string Conteudo { get; init; } = string.Empty;
}

Hub recebendo stream do cliente

using Microsoft.AspNetCore.SignalR;

public sealed class ComandoHub : Hub
{
public async Task ReceberRegistrosCliente(
IAsyncEnumerable<RegistroClienteDto> registros,
CancellationToken cancellationToken)
{
await foreach (var registro in registros.WithCancellation(cancellationToken))
{
Console.WriteLine($"Recebido: {registro.RequestId} - {registro.Conteudo}");

// Exemplo:
// await filaInterna.EnqueueAsync(registro, cancellationToken);
// await banco.GravarAsync(registro, cancellationToken);
}
}
}

Cliente enviando stream

using System.Threading.Channels;
using Microsoft.AspNetCore.SignalR.Client;

var channel = Channel.CreateUnbounded<RegistroClienteDto>();

await connection.SendAsync("ReceberRegistrosCliente", channel.Reader);

for (int i = 1; i <= 10; i++)
{
await channel.Writer.WriteAsync(new RegistroClienteDto
{
RequestId = Guid.NewGuid(),
Conteudo = $"Registro {i}"
});

await Task.Delay(300);
}

channel.Writer.Complete();

Exemplo 4: streaming com cancelamento

O cancelamento é essencial para não deixar processamento preso quando o cliente desconecta.

public async IAsyncEnumerable<ProgressoComandoDto> Monitorar(
[EnumeratorCancellation] CancellationToken cancellationToken)
{
int contador = 0;

while (!cancellationToken.IsCancellationRequested)
{
yield return new ProgressoComandoDto
{
RequestId = Guid.NewGuid(),
Percentual = contador,
Mensagem = $"Evento {contador}"
};

contador++;

await Task.Delay(1000, cancellationToken);
}
}

Cuidados importantes

CuidadoMotivo
Sempre use CancellationTokenevita stream infinito após desconexão
Evite stream sem limitepode consumir memória e CPU
Não use para payload gigante únicoprefira HTTP download/upload
Controle frequência de envioevita pressão no WebSocket
Use MessagePack se tiver alto volumereduz payload
Trate exceções no produtorevita stream travado

Streaming não é fila

SignalR streaming não substitui RabbitMQ.

SignalR streamingRabbitMQ
conexão ativamensagem persistente
tempo realassíncrono/durável
cliente conectadocliente pode estar offline
sem garantia durávelpode ter confirmação e persistência

Para o seu cenário:

  • SignalR streaming: progresso em tempo real para cliente conectado
  • RabbitMQ: resposta durável, processamento assíncrono, retry e persistência

Exemplo aplicado ao seu cenário

Fluxo:

API recebe comando

SignalR envia comando ao desktop

desktop começa execução

desktop envia progresso via stream

API acompanha progresso

resultado final pode ir para RabbitMQ

Melhor uso

Comando curto → SendAsync
Comando com progresso → Streaming
Resposta durável → RabbitMQ
Arquivo grande → HTTP upload/download