Long polling via Redis Pub/Sub com suporte a CancellationToken configurável e notificação por evento ao receber mensagem antes do timeout.
$ dotnet add package MVFC.LongPollingUma biblioteca leve e eficiente de long polling via Redis Pub/Sub para .NET,
com suporte a CancellationToken configurável e payload tipado.
Permitir que clientes HTTP aguardem resultados assíncronos (jobs, webhooks, processamentos) de forma simples, sem polling cego ou WebSockets, usando o canal Pub/Sub do Redis como mecanismo de notificação.
LongPollingOptions.WaitAsync<T> desserializa o resultado diretamente via System.Text.Json.PublishAsync retorna false se nenhum subscriber estava ativo.WaitUntilReadyAsync garante que a subscription está ativa antes de publicar.Program.cs.KeyPrefix.dotnet add package MVFC.LongPolling
builder.Services.AddLongPolling("localhost:6379", cfg =>
{
cfg.DefaultTimeout = TimeSpan.FromSeconds(30);
cfg.KeyPrefix = "poll";
});
IConnectionMultiplexer existentebuilder.Services.AddLongPolling(existingMultiplexer, cfg =>
{
cfg.DefaultTimeout = TimeSpan.FromSeconds(20);
});
app.MapGet("/poll/{jobId}", async (
string jobId,
ILongPollingService polling,
CancellationToken ct) =>
{
var result = await polling.WaitAsync(jobId, cancellationToken: ct);
return result is null
? Results.NoContent() // timeout
: Results.Ok(result);
});
app.MapGet("/poll/{jobId}/typed", async (
string jobId,
ILongPollingService polling,
CancellationToken ct) =>
{
var result = await polling.WaitAsync<OrderCompletedEvent>(jobId, cancellationToken: ct);
return result is null
? Results.NoContent()
: Results.Ok(result);
});
app.MapPost("/notify/{jobId}", async (
string jobId,
NotifyRequest req,
ILongPollingService polling) =>
{
var delivered = await polling.PublishAsync(jobId, req.Payload);
return delivered
? Results.Accepted()
: Results.NotFound($"Nenhum subscriber ativo para o canal '{jobId}'.");
});
var options = new LongPollingOptions(
Timeout: TimeSpan.FromSeconds(10),
KeyPrefix: "custom");
var result = await polling.WaitAsync(jobId, options, cancellationToken: ct);
| Parâmetro | Tipo | Padrão | Descrição |
|---|---|---|---|
DefaultTimeout | TimeSpan | 30 segundos | Tempo máximo de espera por mensagem |
KeyPrefix | string | longpolling | Prefixo do canal Redis |
Cliente HTTP Servidor Redis
──────────── ──────── ─────
GET /poll/{id} ──► WaitAsync()
SubscribeAsync(canal) ──► SUBSCRIBE poll:id
WaitUntilReadyAsync() ◄── (confirmado)
◄── Worker: PublishAsync()
mensagem recebida ◄── PUBLISH poll:id payload
GET retorna ◄── Results.Ok(payload)
Cliente HTTP API Pedidos API Pagamentos
──────────── ─────────── ──────────────
POST /orders ──► Cria pedido
WaitAsync(orderId) ──► Processa pagamento
PublishAsync(orderId, "approved")
◄── "approved"
200 OK ◄── Results.Ok(status)
Cliente HTTP API Pedidos API Pagamentos
──────────── ─────────── ──────────────
POST /orders ──► Cria pedido
WaitAsync(orderId) ──► Processa pagamento
(aguarda...) (sem PublishAsync)
timeout atingido
504 GW Timeout ◄── Results.StatusCode(504)
Cliente HTTP API Pedidos API Pagamentos
──────────── ─────────── ──────────────
POST /orders ──► Cria pedido
WaitAsync(orderId) ──► Processa pagamento
PublishAsync(orderId, "rejected")
◄── "rejected"
422 Unprocessable ◄── Results.UnprocessableEntity(status)
Cliente HTTP API Pedidos API Pagamentos
──────────── ─────────── ──────────────
POST /orders ──► Cria pedido
WaitAsync(orderId) ──► Processa pagamento
✗ desconecta
CancellationToken cancelado
WaitAsync lança OperationCanceledException
(resposta descartada)
Cliente HTTP API Pedidos API Pagamentos
──────────── ─────────── ──────────────
PublishAsync(orderId, "approved")
delivered = false
(nenhum WaitAsync ativo para orderId)
404 Not Found ◄── Results.NotFound(...)
MVFC.LongPolling.Apache License 2.0. Consulte o arquivo LICENSE.