在最近的开发中,我遇到了这样一个需求:一次 HTTP 请求中,需要根据业务场景判断用户数据是否完整。如果数据不完整,逻辑流程需要暂停,提示用户补充必要信息。等用户补全数据后,系统再继续执行剩下的操作。
这个需求在传统的 ASP.NET 应用中并不常见,因为 HTTP 请求通常是无状态且一次性处理完毕的,中途“暂停再继续”的流程并不容易实现。接下来我将分享我是如何在 ASP.NET 中实现这个功能的。后端代码:
using Microsoft.AspNetCore.SignalR;using Microsoft.Extensions.Caching.Distributed;using System.Collections.Concurrent;
var builder = WebApplication.CreateBuilder(args);builder.Services.AddDistributedMemoryCache();builder.Services.AddSingleton<PendingRequestManager>();builder.Services.AddControllers();builder.Services.AddSignalR();builder.Services.AddCors(options =>{ options.AddDefaultPolicy(policy => { policy.AllowAnyOrigin(); });});var app = builder.Build();app.UseCors();app.MapControllers();app.MapHub<NotifyHub>("/notifyHub");app.UseStaticFiles();app.MapPost("consume", async (string userId, PendingRequestManager manager, IDistributedCache cache, IHubContext<NotifyHub> hubContext, CancellationToken cancellationToken) =>{ var tcs = manager.Create(userId); await hubContext.Clients.Group(userId).SendAsync("NeedSupplement", "服务器返回:请补充数据哦!!!!"); // 挂起请求,等待补充,最多300秒 var finished = await Task.WhenAny(tcs.Task, Task.Delay(TimeSpan.FromSeconds(300))); var message = await cache.GetStringAsync(userId, cancellationToken); Console.WriteLine(message); if (finished != tcs.Task) { manager.Remove(userId); return Results.BadRequest("服务器返回:等待超时"); } return Results.Ok("服务器返回:处理成功!");});app.MapPost("/supplement", async (string userId, DataMessage dataMessage, IDistributedCache cache, PendingRequestManager manager, CancellationToken cancellationToken) =>{ await cache.SetStringAsync(userId, dataMessage.Data, new DistributedCacheEntryOptions { SlidingExpiration = TimeSpan.FromSeconds(2) }, cancellationToken); // 实际上你应该验证数据并补充 var tcs = manager.Get(userId); if (tcs != null) { tcs.SetResult(true); manager.Remove(userId); } return Results.Ok("服务器返回:发送补充成功");});app.Run();
public class PendingRequestManager{ private ConcurrentDictionary<string, TaskCompletionSource<bool>> _pendingRequests = new(); public TaskCompletionSource<bool> Create(string userId) { var tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously); _pendingRequests[userId] = tcs; return tcs; } public TaskCompletionSource<bool>? Get(string userId) { _pendingRequests.TryGetValue(userId, out var tcs); return tcs; } public void Remove(string userId) { _pendingRequests.TryRemove(userId, out _); }}
public class NotifyHub : Hub{ public override Task OnConnectedAsync() { var userId = Context.GetHttpContext()?.Request.Query["userId"]; if (!string.IsNullOrEmpty(userId)) { Groups.AddToGroupAsync(Context.ConnectionId, userId); } return base.OnConnectedAsync(); }}public class DataMessage{ public string Data { get; set; }}
前端代码:
<!DOCTYPE html><html><head> <meta charset="utf-8"> <title>SignalR补数据Demo</title> <script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/7.0.5/signalr.min.js"></script></head><body> <button id="consume">开始处理数据</button> <br /> <textarea id="data" style="display:none;" rows="4" cols="50">我是浏览器上补的数据Data</textarea> <br /> <button id="supplement" style="display:none;">开始补数据</button> <div id="msg"></div>
<script> const userId = "user1"; let connection = new signalR.HubConnectionBuilder() .withUrl("/notifyHub?userId=" + userId) .build();
connection.on("NeedSupplement", function (message) { document.getElementById('msg').innerText = message; document.getElementById('supplement').style.display = ""; document.getElementById('data').style.display = ""; });
connection.start().then(function () { console.log("SignalR Connected."); }).catch(function (err) { return console.error(err.toString()); });
document.getElementById('consume').onclick = function() { fetch('/consume?userId=' + userId, { method: 'POST' }) .then(resp => resp.json()) .then(data => { document.getElementById('msg').innerText = JSON.stringify(data); }); };
document.getElementById('supplement').onclick = function() { fetch('/supplement?userId=' + userId, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ data: document.getElementById('data').value, }) }) .then(resp => resp.json()) .then(data => { document.getElementById('msg').innerText = JSON.stringify(data); document.getElementById('supplement').style.display = "none"; document.getElementById('data').style.display = "none"; }); }; </script></body></html>
TaskCompletionSource是什么?
- TaskCompletionSource<T> 是 .NET 提供的一个可以手动控制 Task 何时完成的对象。
- 用它你可以在某处 await 一个 Task,而在别的地方(甚至别的方法/线程)决定 Task 什么时候完成(SetResult/SetException/SetCanceled)。
- 适用于异步等待外部事件/条件的场景。
声明:来自硅基-桂迹,仅代表创作者观点。链接:https://eyangzhen.com/1893.html