【问题标题】:ASP .NET Core 2.1-preview2 HttpClient deadlockASP .NET Core 2.1-preview2 HttpClient 死锁
【发布时间】:2018-05-15 20:45:36
【问题描述】:

我在 Azure Web 应用程序上托管了 ASP.NET Core 2.1 应用程序。我通过 WebSockets 发送照片 base64 字符串,然后通过 HttpClient 发送到 Azure Face API。

大约 150-250 个请求后,HttpClient 停止响应,我无法在应用程序的任何部分使用 HttpClient 类。

在我的本地主机中它可以正常工作,我从来没有遇到过这个问题。

public class FaceApiHttpClient
{
    private HttpClient _client;

    public FaceApiHttpClient(HttpClient client)
    {
        _client = client;
    }

    public async Task<string> GetStringAsync(byte[] byteData,string uri)
    { 
        using (ByteArrayContent content = new ByteArrayContent(byteData))
        {
            content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");

            HttpResponseMessage response = await _client.PostAsync(uri, content).ConfigureAwait(false);

            return await response.Content.ReadAsStringAsync().ConfigureAwait(false);
        }

    }
}

DI:

         services.AddHttpClient<FaceApiHttpClient>(
            client => {
                client.BaseAddress = new Uri("xxx");
                client.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", "xxx");
            });

FaceApiClient 的方法在 Scoped Service 中调用:

public interface IFaceAPIService
{
    Task<DataServiceResult<List<Face>>> GetFacesDataFromImage(byte[] byteArray);
}

public class FaceAPIService: ServiceBase, IFaceAPIService
{
    private readonly IServerLogger _serverLogger;
    private FaceApiHttpClient _httpClient;
    //Consts
    public const string _APIKey = "xxx";
    public const string _BaseURL = "xxx";

    public FaceAPIService(IServerLogger serverLogger, FaceApiHttpClient client)
    {
        _serverLogger = serverLogger;
        _httpClient = client;          
    }

    public async Task<DataServiceResult<List<Face>>> GetFacesDataFromImage(byte[] byteData)
    {
        try
        {
            // Request parameters. A third optional parameter is "details".
            string requestParameters = "returnFaceId=true&returnFaceLandmarks=false&returnFaceAttributes=age,gender,headPose,smile,facialHair,glasses,emotion,hair,makeup,occlusion,accessories,blur,exposure,noise";

            // Assemble the URI for the REST API Call.
            string uri = _BaseURL + "/detect" + "?" + requestParameters;
            var result = await _httpClient.GetStringAsync(byteData, uri).ConfigureAwait(false);
            List<Face> faces = JsonConvert.DeserializeObject<List<Face>>(result);
            return Success(faces);

        }
        catch (Exception ex)
        {
            _serverLogger.LogExceptionFromService(ex);
            return DataServiceResult.ErrorResult<List<Face>>(ex.Message);
        }
    }
}

a) 在 localhost 环境下它可以工作。我运行了 11 个模拟器,每秒有很多请求,但它从未中断(10 小时的模拟器,超过 2 万个请求)。

b) HttpClient 不仅在一个类中停止在应用程序的任何部分工作。

如何解决这个问题?

【问题讨论】:

  • HttpClient 是如何注册到服务集合的?不建议创建过多的 HttpClient 实例。
  • 由Services.AddHttpClient在Startup.cs类中添加
  • 不知道是不是问题的根源,但是在使用共享客户端实例的时候不要重复添加DefaultRequestHeaders。事实上,除非所有请求都需要相同的标题,否则根本不应该这样添加标题。
  • 死锁意味着并发问题,但我认为您的情况并非如此。这听起来更像是你的套接字用完了。你在哪里托管它?天蓝色?哪一层?它们通常对可以打开的套接字数量有限制,具体取决于 VM/计划层
  • 你没有处理你的HttpResponseMessage response。这可能是原因,但即使不值得这样做。

标签: c# asp.net-core


【解决方案1】:

考虑稍微改变一下设计。

使用类型化客户端的假设是它的配置不会经常更改,并且应该在注册类型化客户端时添加一次。

services.AddHttpClient<FaceApiHttpClient>(_ => {
    _.BaseAddress = new Uri(Configuration["OcpApimBaseURL"]);
    var apiKey = Configuration["OcpApimSubscriptionKey"];
    _.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", apiKey);
    _.Timeout = new TimeSpan(0, 0, 10);
});

这将允许键入的客户端不必为每次调用添加密钥

public class FaceApiHttpClient {
    private readonly HttpClient client;

    public FaceApiHttpClient(HttpClient client) {
        this.client = client;
    }

    public async Task<string> GetStringAsync(byte[] byteData, string uri) {            
        using (var content = new ByteArrayContent(byteData)) {
            // This example uses content type "application/octet-stream".
            // The other content types you can use are "application/json" and "multipart/form-data".
            content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");

            // Execute the REST API call.
            HttpResponseMessage response;  response = await _client.PostAsync(uri, content).ConfigureAwait(false);

            // Get the JSON response.
            return await response.Content.ReadAsStringAsync().ConfigureAwait(false);
        }
    }
}

注意来自ASP.NET Core 2.1-preview1: Introducing HTTPClient factory

类型化客户端实际上是一种临时服务,这意味着每次需要时都会创建一个新实例,并且每次构造它时都会接收一个新的 HttpClient 实例。这意味着配置函数,在这种情况下从配置中检索 URI,将在每次需要 FaceApiHttpClient 时运行。

根据之前的文档,创建这么多客户端可能会带来问题,但这里的假设是这个新功能的开发人员在设计时考虑到了这一点。

我这样说是因为您描述的问题与之前的问题相似,原因相同。

【讨论】:

  • 我根据您的建议进行了测试,但仍然无法正常工作。 50 次请求后出现死锁。在没有死锁的 localhost 20k+ 请求上。
  • @NorbertPisz,我认为您很可能会用完套接字。要测试这个理论,请尝试恢复到以前使用HttpClient 的方式。创建一个单例HttpClient 并仅使用FaceApiHttpClient 中的那个。测试看看是否会出现同样的问题。可能是预览框架还没有准备好。
  • 不,我将测试 2 种方法:a) 我将 HttpClient 添加为单例 b) 我将在我的 WebSockets 单例类中使用一个 HttpClient 实例。我会让你知道结果。谢谢!
  • 你知道如何得到这个死锁的详细错误吗?
  • 我已升级到 ASP.NET Core 2.1 RC1,将其作为自包含应用程序(即我将框架版本与应用程序一起部署)部署到 Azure 应用服务,并且到目前为止,我再也没有遇到过这个问题。 Preview2 或 Preview2 的 App Service 扩展中一定有一些奇怪之处。当应用服务推出 RC1 时,我将切换回依赖于框架的部署(期望框架在机器上)。当然,部署占用空间要小得多。
【解决方案2】:

ASP .NET CORE 2.1 RC1 版本发布后问题得到修复。我将项目更新到新版本,现在死锁没有问题。

死锁的问题只存在于ASP .NET CORE 2.1 Preview 2版本中。

【讨论】:

    猜你喜欢
    • 2019-04-03
    • 2018-11-17
    • 2019-11-17
    • 2018-12-15
    • 2020-03-22
    • 1970-01-01
    • 2019-01-24
    • 2019-02-20
    • 2018-12-25
    相关资源
    最近更新 更多