ASP .NET Core 2.1-preview2 HttpClient死锁

时间:2018-04-14 12:42:47

标签: c# asp.net-core

我在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小时的模拟器,超过20k的请求)。

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

如何解决这个问题?

2 个答案:

答案 0 :(得分:2)

考虑改变设计。

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

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时运行。

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

我之所以这样说,是因为您描述的问题与之前出现的问题类似。

答案 1 :(得分:1)

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

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