如何在REST请求中实现异步回调?附C# REST服务示例
在C# Web API中实现REST请求的异步回调方案
针对你的匹配场景(玩家发起请求加入匹配队列,匹配成功后需要通知客户端),以下是几种适合的异步回调实现方案,都是基于你现有的Web API架构设计:
方案1:客户端提供回调URL(Webhook模式)
这是最直接的方案:客户端在请求时附带一个自己可以接收通知的URL,服务端完成匹配后,主动调用这个URL把结果推送给客户端。
代码示例
首先修改你的控制器方法,接收回调URL参数:
public class MatchController : ApiController { // 注入后台匹配服务(建议用IHostedService或可靠任务框架管理) private readonly IMatchmakingService _matchmakingService; public MatchController(IMatchmakingService matchmakingService) { _matchmakingService = matchmakingService; } [Route("FindMatchAsIndividual")] [HttpPost] public async Task<HttpResponseMessage> FindMatchAsIndividual(string playername, string callbackUrl) { // 验证回调URL合法性(可选,防止恶意地址) if (!Uri.TryCreate(callbackUrl, UriKind.Absolute, out _)) { return Request.CreateErrorResponse(HttpStatusCode.BadRequest, "无效的回调URL"); } // 将玩家和回调地址加入匹配队列,后台异步处理匹配 await _matchmakingService.AddPlayerToQueueAsync(playername, callbackUrl); // 立即返回响应,不阻塞客户端 return Request.CreateResponse(HttpStatusCode.Accepted, "已加入匹配队列,匹配成功后会通知你"); } }
然后实现后台匹配服务(用BackgroundService管理后台任务,避免ASP.NET回收临时线程):
public interface IMatchmakingService { Task AddPlayerToQueueAsync(string playername, string callbackUrl); } public class MatchmakingService : BackgroundService, IMatchmakingService { private readonly ConcurrentQueue<PlayerMatchRequest> _matchQueue = new(); private readonly HttpClient _httpClient = new(); protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { // 模拟匹配逻辑:检查队列是否有可匹配的玩家 if (_matchQueue.TryPeek(out _) && _matchQueue.Count >= 2) { var player1 = _matchQueue.Dequeue(); var player2 = _matchQueue.Dequeue(); // 构造匹配结果 var matchResult = new MatchResult { MatchId = Guid.NewGuid().ToString(), Players = new List<string> { player1.PlayerName, player2.PlayerName } }; // 调用回调URL推送结果 await NotifyPlayerAsync(player1.CallbackUrl, matchResult); await NotifyPlayerAsync(player2.CallbackUrl, matchResult); } await Task.Delay(1000, stoppingToken); } } public async Task AddPlayerToQueueAsync(string playername, string callbackUrl) { _matchQueue.Enqueue(new PlayerMatchRequest { PlayerName = playername, CallbackUrl = callbackUrl }); await Task.CompletedTask; } private async Task NotifyPlayerAsync(string callbackUrl, MatchResult matchResult) { try { await _httpClient.PostAsJsonAsync(callbackUrl, matchResult); } catch (Exception ex) { // 处理回调失败:可加入重试队列或记录日志 Console.WriteLine($"通知玩家失败:{ex.Message}"); } } private class PlayerMatchRequest { public string PlayerName { get; set; } public string CallbackUrl { get; set; } } private class MatchResult { public string MatchId { get; set; } public List<string> Players { get; set; } } }
注意点
- 优先用Hangfire、Quartz.NET这类可靠任务框架替代简单线程,避免服务重启丢失匹配请求。
- 必须添加回调URL验证逻辑,防止注入恶意地址。
- 实现重试机制,应对客户端暂时不可达的情况。
方案2:长轮询(Long Polling)
如果客户端不想暴露回调URL,可以用长轮询:客户端发起请求后,服务端hold住连接,直到匹配成功或超时再返回结果。
代码示例
public class MatchController : ApiController { private readonly IMatchmakingService _matchmakingService; public MatchController(IMatchmakingService matchmakingService) { _matchmakingService = matchmakingService; } [Route("FindMatchAsIndividual")] [HttpPost] public async Task<HttpResponseMessage> FindMatchAsIndividual(string playername) { // 设置30秒超时 var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30)); try { var matchResult = await _matchmakingService.WaitForMatchAsync(playername, cts.Token); return Request.CreateResponse(HttpStatusCode.OK, matchResult); } catch (OperationCanceledException) { return Request.CreateResponse(HttpStatusCode.RequestTimeout, "匹配超时,请重新发起请求"); } } }
匹配服务中实现等待逻辑:
public async Task<MatchResult> WaitForMatchAsync(string playername, CancellationToken cancellationToken) { var tcs = new TaskCompletionSource<MatchResult>(); // 将玩家和结果等待器加入队列 _matchQueue.Enqueue(new PlayerMatchRequest { PlayerName = playername, MatchResultTcs = tcs }); // 等待结果或取消 using (cancellationToken.Register(() => tcs.TrySetCanceled())) { return await tcs.Task; } } // 匹配成功时触发结果返回 // tcs.SetResult(matchResult);
注意点
- 长轮询会占用服务端连接数,不适合超高并发场景。
- 合理设置超时时间,避免连接长时间占用资源。
方案3:SignalR(双向实时通信)
如果你的场景对实时性要求很高,推荐用ASP.NET官方的SignalR框架,它封装了WebSocket、长轮询等底层实现,能让服务端主动推送消息给客户端。
代码示例
首先安装SignalR包:Install-Package Microsoft.AspNet.SignalR
创建SignalR Hub:
public class MatchHub : Hub { private readonly IMatchmakingService _matchmakingService; public MatchHub(IMatchmakingService matchmakingService) { _matchmakingService = matchmakingService; } // 客户端调用此方法加入匹配 public async Task JoinMatchAsIndividual(string playername) { // 关联客户端连接ID和玩家名 await _matchmakingService.AddPlayerToQueueAsync(playername, Context.ConnectionId); } }
修改匹配服务,匹配成功时推送结果:
private async Task NotifyPlayerAsync(string connectionId, MatchResult matchResult) { var hubContext = GlobalHost.ConnectionManager.GetHubContext<MatchHub>(); hubContext.Clients.Client(connectionId).ReceiveMatchResult(matchResult); }
客户端(JS)连接并接收消息:
var connection = $.hubConnection(); var matchHub = connection.createHubProxy('matchHub'); matchHub.on('ReceiveMatchResult', function(matchResult) { console.log('匹配成功!', matchResult); }); connection.start().done(function() { matchHub.invoke('JoinMatchAsIndividual', '你的玩家名'); });
注意点
- SignalR自动处理连接断开重连,比手动实现WebSocket更可靠。
- 适合游戏这类实时性要求高的场景。
内容的提问来源于stack exchange,提问作者Mantracker
相关产品推荐
相关产品推荐

