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

C#使用dotAPNS发送iOS推送运行一段时间失效需重启服务器如何解决

问题根因及修复方案

首先梳理你遇到的运行一段时间后无响应的核心原因,对应修复方案如下:


核心问题点

  • HttpClient实例重复创建导致端口耗尽:你每次调用SendIOSNotification方法都会新建HttpClient实例,HttpClient设计为单例复用,频繁创建会导致服务器TCP端口被快速占满,AWS环境下默认端口回收速度慢,端口耗尽后新的请求无法建立连接,就会卡在发送请求的位置,重启服务器后端口全部释放,所以会恢复正常,完全符合你描述的现象。
  • .Result阻塞异步调用导致死锁:代码使用.Result同步等待异步方法返回,在ASP.NET(代码中存在HttpContext.Current判定为ASP.NET框架)的同步上下文机制下,非常容易出现死锁:异步回调需要等待同步上下文释放才能继续执行,而.Result会阻塞当前线程占用同步上下文,两者互相等待就会出现永久无响应的情况。
  • ApplePush对象复用导致逻辑错误:你在循环外部创建了ApplePush实例,在循环遍历设备token的过程中不断往同一个实例里新增token、自定义属性,会导致后续推送的payload里携带大量前序推送的冗余数据,payload体积不断膨胀,轻则推送逻辑错误,重则触发APNs的请求大小限制,导致请求卡住。

修复方案

1. 改造为单例复用ApnsClient

将ApnsClient的初始化逻辑从方法中移出,改为全局单例,整个应用生命周期只初始化一次:

// 全局静态字段,仅初始化一次
private static readonly ApnsClient _apnsClient;

// 应用启动时执行一次初始化(比如Global.asax的Application_Start方法里)
static YourClassName()
{
    string p8File = HttpContext.Current.Server.MapPath("*******");
    var options = new ApnsJwtOptions()
    {
        BundleId = "*****",
        CertFilePath = p8File,
        KeyId = "******",
        TeamId = "*****"
    };
    // 给HttpClient设置超时,避免永久卡住
    var httpClient = new HttpClient(new WinHttpHandler())
    {
        Timeout = TimeSpan.FromSeconds(15)
    };
    _apnsClient = ApnsClient.CreateUsingJwt(httpClient, options);
}

2. 修复异步调用和ApplePush复用问题

方法改为异步实现,避免死锁,每次循环新建ApplePush实例:

public async Task<bool> SendIOSNotification(Dictionary<string, Dictionary<string, string>> iosDictionary, ILog log = null)
{
    foreach (var itemKey in iosDictionary)
    {
        _log.DebugFormat("========== IOS ==========");
        _log.DebugFormat("Device Item {0}", itemKey);
        string payLoadCatgory = string.Empty;

        // 每次发新的推送都新建ApplePush实例
        var push = new ApplePush(ApplePushType.Alert)
            .AddBadge(1)
            .AddSound("sound.caf")
            .AddCustomProperty("content-available", "1", true)
            .AddToken(itemKey.Key.ToString());                              

        foreach (var itemValue in itemKey.Value)
        {
            try
            {
                if (!itemValue.Key.ToLower().Contains("id"))
                {
                    push.AddCustomProperty(itemValue.Key, itemValue.Value, true);
                }
                else
                {
                    push.AddCustomProperty(itemValue.Key, Convert.ToInt64(itemValue.Value), true);
                }

                if (itemValue.Key.ToLower().Contains("category"))
                {
                    payLoadCatgory = itemValue.Value;
                }
            }
            catch (Exception ex)
            {
                log.Error(string.Format("Error in send Notification to IOS :: DeviceToken is {0}", itemKey.Key));
            }
        }
        try
        {
            _log.Debug("Start send notification");
            // 用await替代.Result,避免死锁
            var response = await _apnsClient.SendAsync(push).ConfigureAwait(false);
            _log.Debug("Sent notification");
            if (response.IsSuccessful)
            {
                log.Info(string.Format("An alert push has been successfully sent!"));
                return true;
            }
            else
            {
                switch (response.Reason)
                {
                    case ApnsResponseReason.BadCertificateEnvironment:
                        break;
                    default:
                        throw new ArgumentOutOfRangeException(nameof(response.Reason), response.Reason, null);
                }
                log.Error(string.Format("Failed to send a push, APNs reported an error: " + response.ReasonString));
            }
        }
        catch (TaskCanceledException)
        {
            log.Error(string.Format("Failed to send a push: HTTP request timed out."));
        }
        catch (HttpRequestException ex)
        {
            log.Error(string.Format("Failed to send a push. HTTP request failed: " + ex));
        }
        catch (Exception ex)
        {
            log.Error(string.Format("Failed to send a push. HTTP request failed: " + ex));
            return false;
        }
    }
    return false;
}

如果实在不能改方法为异步,也可以用以下方式替代.Result避免死锁:

var response = _apnsClient.SendAsync(push).ConfigureAwait(false).GetAwaiter().GetResult();

内容的提问来源于stack exchange,提问作者Pummy Kumari

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.30 20:24:04