You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 03:27:14