首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >并行任务中运行的C# - HTTP随机失败:复制内容到流时出错

并行任务中运行的C# - HTTP随机失败:复制内容到流时出错
EN

Stack Overflow用户
提问于 2021-03-24 21:25:09
回答 1查看 704关注 0票数 0

在我的C#应用程序(.NET Core3.1)中,有一个自动任务,每X小时启动另一个任务,它以不同的参数多次并行运行。在这个自动任务的末尾,会调用await Task.WhenAll(tasksList).等待并行任务的完成。

每个任务发出一个HTTPClient (使用IHttpClientFactory工厂方法)并发出一个GET请求,语法如下:

代码语言:javascript
运行
复制
var res = await client.GetAsync(url);
if (res.IsSuccessStatusCode)
{
  var exit = await res.Content.ReadAsStringAsync();
  [...omitted]
}

当共享相同的GET的两个任务在最大60-70ms的距离上运行时,问题就会随机发生。有时,这两项任务都会一个接一个地失败,每个任务都有相同的例外:

代码语言:javascript
运行
复制
System.Net.Http.HttpRequestException: Error while copying content to a stream.
 ---> System.IO.IOException: The response ended prematurely.
   at System.Net.Http.HttpConnection.FillAsync()
   at System.Net.Http.HttpConnection.ChunkedEncodingReadStream.CopyToAsyncCore(Stream destination, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionResponseContent.SerializeToStreamAsync(Stream stream, TransportContext context, CancellationToken cancellationToken)
   at System.Net.Http.HttpContent.LoadIntoBufferAsyncCore(Task serializeToStreamTask, MemoryStream tempBuffer)

从日志中,我可以看到服务器如何正确启动和接收两个不同的HTTP请求。

如果删除ReadAsStringAsync部分,问题就不会发生,因此我假设它与内容读取有关(在状态代码检查之后),就好像这两个任务最终共享Get结果一样(同时发出了两个不同的活动连接)。我尝试使用ReadAsStreamAsync,但问题仍然发生(这有助于减少事件发生,尽管如此)。

另一件可能相关的事情是,检索到的结果相当重(上次我下载它时,它的结尾是一个4.5MB的.json文件,或多或少)。

应该按顺序运行每个任务吗?还是我发出的HTTP请求出错了?

如果您想测试这个问题,在这里您可以找到一个控制台应用程序的源代码,我正在使用它来重现这个问题(如果它在前20个调用之前没有发生,请重新启动该应用程序,直到它发生为止):

代码语言:javascript
运行
复制
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    class Program
    {
        static async Task<int> Main(string[] args)
        {
            var builder = new HostBuilder()
                .ConfigureServices((hostContext, services) =>
                {
                    services.AddHttpClient();
                    services.AddTransient<TaskRunner>();
                }).UseConsoleLifetime();

            var host = builder.Build();
            using (var serviceScope = host.Services.CreateScope())
            {
                var services = serviceScope.ServiceProvider;
                try
                {
                    var x = services.GetRequiredService<TaskRunner>();
                    var result = await x.Run();

                    Console.WriteLine(result);
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Error Occured");
                }
            }

            return 0;
        }

        public class TaskRunner
        {
            private static IHttpClientFactory _httpFactory { get; set; }
            public TaskRunner(IHttpClientFactory httpFactory)
            {
                _httpFactory = httpFactory;
            }
            public async Task<string> Run()
            {
                Console.WriteLine("Starting loop...");
                do
                {
                    await Task.Delay(2500); // wait app loading
                    await SendRequest();
                } while (true);
            }
            private static async Task SendRequest()
            {
                await Task.WhenAll(new Task[] { ExecuteCall(), ExecuteCall()};
            }
            private async static Task<bool> ExecuteCall()
            {
                try
                {
                    var client = _httpFactory.CreateClient();
                    // fake heavy API call (> 5MB data)
                    var api = "https://api.npoint.io/5896085b486eed6483ce";
                    Console.WriteLine("Starting call at " + DateTime.Now.ToUniversalTime().ToString("o"));
                    var res = await client.GetAsync(api);
                    if (res.IsSuccessStatusCode)
                    {
                        var exit = await res.Content.ReadAsStringAsync();
                        /* STREAM read alternative
                        var ed = await res.Content.ReadAsStreamAsync();
                        StringBuilder result = new StringBuilder();
                        using var sr = new StreamReader(ed);
                        while (!sr.EndOfStream)
                        {
                            result.Append(await sr.ReadLineAsync());
                        }
                        var exit = result.ToString();
                        */
                        Console.WriteLine(exit.Substring(0, 10));
                        //Console.WriteLine(exit);
                        
                        Console.WriteLine("Ending call at " + DateTime.Now.ToUniversalTime().ToString("o"));
                        return true;
                    }
                    Console.WriteLine(res.StatusCode);
                        Console.WriteLine("Ending call at " + DateTime.Now.ToUniversalTime().ToString("o"));
                    return false;
                }
                catch (Exception ex)
                {
                    // put breakpoint here
                    // Exception => called on line:78 but if content isn't read it never occurs
                    Console.WriteLine(ex.ToString());
                    return false;
                }
            }
        }
    }
}

谢谢你能给我的任何帮助/建议!

EN

回答 1

Stack Overflow用户

发布于 2021-05-14 14:51:38

我回答我的问题是为了离开我申请的解决方案,对于任何可能遇到相同问题的人:)

我在Api调用之前添加了以下行:

代码语言:javascript
运行
复制
var client = _httpFactory.CreateClient();
var api = "https://api.npoint.io/5896085b486eed6483ce";

>>> client.DefaultRequestVersion = HttpVersion.Version10; // new line

var res = await client.GetAsync(api);

这个问题似乎与端点服务器有关,即在HttpVersion为11时丢弃并发连接。这可能与保持活动连接头有关,因为在10 v时,标头被设置为关闭。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/66789634

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档