并行 ASP.NET API 调用抛出“底层连接已关闭:连接意外关闭。”

问题描述 投票:0回答:1

我有一个 ASP.NET MVC (.NET 4.8) 应用程序,它调用 ASP.NET Core 8 Web API 作为生成 PDF 文档的服务。我们称它们为“主要”和“服务”。

PDF 发布针对一个或多个国家循环完成,数据获取和制图完全同步。数据准备好后,我将针对每个国家/地区并行发送一个请求到 service 应用程序。

服务使用 Razor 生成 HTML,并使用 HTML 到 PDF 库 (IronPDF) 将其转换为 PDF。该文件作为响应发送回main应用程序。

重命名并省略不必要部分后的代码如下。

主要:

public async Task PublishDocuments(List<string> countries)
{
    var requests = new Dictionary<string, PublishRequest>();

    foreach (var country in countries)
    {
        // prepare data synchronously
        // add data to requests Dictionary
    }

    using var httpClientHandler = new HttpClientHandler();
    httpClientHandler.ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => true;

    using var httpClient = new HttpClient(httpClientHandler);
    httpClient.Timeout = TimeSpan.FromMinutes(15);

    var publishTasks = requests.Select(x => GetAndSaveFile(x.Value, x.Key, httpClient));
    await Task.WhenAll(publishTasks);

    // more DB work
}

private async Task GetAndSaveFile(PublishRequest request, string fullFilePath, HttpClient httpClient)
{
    var res = await GetDocument(request, httpClient);
    File.WriteAllBytes(fullFilePath, res);
}

private async Task<byte[]> GetDocument(PublishRequest request, HttpClient httpClient)
{
    var serializedRequest = JsonConvert.SerializeObject(request);

    var content = new StringContent(serializedRequest, System.Text.Encoding.UTF8, "application/json");
    var response = await httpClient.PostAsync("https://localhost:7000/publish", content);

    return await response.Content.ReadAsByteArrayAsync();
}

服务:

app.MapPost("/publish", async (PublishRequest request, HttpContext context) =>
{
    // We are using HTML to PDF library, and rely on Razor for strict typing and intellisense in HTML
    var htmlContent = await RenderRazorViewToStringAsync(context, "Document", request);
    var renderer = new ChromePdfRenderer();

    renderer.RenderingOptions.EnableJavaScript = true;
    renderer.RenderingOptions.WaitFor.JavaScript(500);

    var pdf = renderer.RenderHtmlAsPdf(htmlContent);

    context.Response.ContentType = "application/pdf";
    context.Response.StatusCode = 200;
    context.Response.Headers.Add("Content-Disposition", "attachment; filename=generated.pdf");
    await context.Response.Body.WriteAsync(pdf.BinaryData);
}).WithRequestTimeout(TimeSpan.FromMinutes(10));

这适用于一定数量的国家(即请求)。但是,如果一次发送太多请求,我就会开始收到这些错误,首先是

PublishDocuments
:

System.Net.Http.HttpRequestException:“发送请求时发生错误。”
在 System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() > 在 System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(任务任务)
在 System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
在 PublishService.d__22.MoveNext() 中...

有一个内部例外:

底层连接已关闭:连接意外关闭。
在 System.Net.HttpWebRequest.EndGetResponse(IAsyncResult asyncResult)
在 System.Net.Http.HttpClientHandler.GetResponseCallback(IAsyncResult ar)

GetDocument

将内容复制到流时出错。
在 System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
在 System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(任务任务)
在 System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
在 PublishService.d__22.MoveNext() 在 ...

有一个内在的例外:

无法从传输连接读取数据:连接已关闭。
在 System.Net.ConnectStream.IOError(异常异常,布尔值 willThrow)
在 System.Net.ConnectStream.InternalWrite(布尔异步,Byte[] 缓冲区,Int32 偏移量,Int32 大小,AsyncCallback 回调,对象状态)
在 System.Net.ConnectStream.BeginWrite(Byte[] 缓冲区,Int32 偏移量,Int32 大小,AsyncCallback 回调,对象状态)
在 System.Threading.Tasks.TaskFactory

1.FromAsyncImpl[TArg1,TArg2,TArg3](Func
6 beginMethod、Func
2 endFunction, Action
1 endAction、TArg1 arg1、TArg2 arg2、TArg3 arg3、对象状态、TaskCreationOptions 创建选项)
在 System.Net.Http.ByteArrayContent.SerializeToStreamAsync(流 流,TransportContext 上下文)
在 System.Net.Http.HttpContent.CopyToAsync(流流,TransportContext 上下文)

我尝试了这些建议,其中大部分仍然存在于代码中:

  1. 增加

    HttpClient
    实例的超时:

     httpClient.Timeout = TimeSpan.FromMinutes(15);
    
  2. Expect-100
    设置为 false(当它没有帮助时我将其删除):

     System.Net.ServicePointManager.Expect100Continue = false;
    
  3. HttpClient
    传递到
    GetDocument
    ,而不是每个请求创建一个
    HttpClient

  4. 增加 service API 方法的请求超时:

    .WithRequestTimeout(TimeSpan.FromMinutes(10));

我仍然有同样的行为。这全部在我的本地机器上。

有人知道我还能尝试什么吗?

c# asp.net-core asp.net-core-webapi dotnet-httpclient
1个回答
0
投票

当您用多个并发请求淹没它时,我怀疑您向其发送 HTTP 请求的第 3 方服务器出现故障。

您应该限制并发请求。

使用

SemaphoreSlim
实现锁定机制的示例:

private const int MaxNumberOfConcurrentCalls = 3; // choose the appropriate number
private readonly SemaphoreSlim semaphore = new SemaphoreSlim(0, MaxNumberOfConcurrentCalls);

private async Task GetAndSaveFile(PublishRequest request, string fullFilePath, HttpClient httpClient)
{
    await semaphore.WaitAsync();
    try
    {
        var res = await GetDocument(request, httpClient);
        File.WriteAllBytes(fullFilePath, res);
    }
    finally
    {
        semaphore.Release();
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.