Задать вопрос
@O_oo

Как правильно реализовать асинхронный httpclient C#?

Есть на локальном компьютере Orthanc-server, который у меня выступает имитацией, соответственно, сервера данных. Его задача отдавать мне по API JSON-файл, который содержит теги DICOM-файла на сервере. Эти данные нужно парсить. Т.к. их огромное количество (около 10 000 на тестовой выборке), то нужно это делать все параллельно. Основной код написан, представляю ниже:
Блок main
Dictionary<string, string> Datas = new()
{
    {"Instances","http://localhost:8042/instances" },
    {"Series","http://localhost:8042/series" },
    {"Patients","http://localhost:8042/patients" },
    {"Studies","http://localhost:8042/studies" }
};
Instance Instances = new();
Seriee Series = new();
Patients Patients = new();
Study Studies = new();
List<string> exceptions = new();
Task[] tasks = new Task[4];
HttpClient Client = new();
tasks[0] = Instances.DataGetter(Datas.GetValueOrDefault("Instances"), Client, exceptions);
tasks[1] = Series.DataGetter(Datas.GetValueOrDefault("Series"), Client, exceptions);
tasks[2] = Patients.DataGetter(Datas.GetValueOrDefault("Patients"), Client, exceptions);
tasks[3] = Studies.DataGetter(Datas.GetValueOrDefault("Studies"), Client, exceptions);

await Task.WhenAll(tasks);

foreach (var item in exceptions)
{
    Console.WriteLine($"{item}\n");
}
Console.WriteLine($"Всего ошибок:{exceptions.Count}");
Client.Dispose();


Класс с методами парсинга (остальные являются наследниками и переопределяют необходимые поля)
public class Patients
    {
        
        public string? Path { get; set; }
        [JsonProperty (nameof(ID))]
        public string? ID { get; set; }

        public MainDicomTags? mainDicomTags { get; set; }
        public class MainDicomTags
        {
            [JsonProperty(nameof(PatientName))]
            public string? PatientName { get; set; }
            [JsonProperty(nameof(PatientSex))]
            public char? PatientSex { get; set; }
            [JsonProperty("PatientBirthDate")]
            public string? Date { get; set; }
            [JsonProperty(nameof(PatientID))]
            public string? PatientID { get; set; }
        }

        public async Task DataGetter(string url, HttpClient Client, List<string> exceptions)
        {
            var DataIDs = await Client.GetFromJsonAsync<List<string>>(url);
            Task[] DataTasks = new Task[DataIDs.Count];
            for (var i = 0; i < DataTasks.Length; i++)
                DataTasks[i] = GetData(i, url,Client , DataIDs, exceptions);
            await Task.WhenAll(DataTasks);
        }
        public virtual async Task GetData(int index, string url, HttpClient Client, List<string> obj, List<string> exceptions)
        {
            try
            {
               
                Patients? patient = new();
                patient = await Client.GetFromJsonAsync<Patients>($"{url}/{obj[index]}");
                patient.Path = $"{url}/{obj[index]}";
                patient = ToString(ref patient);
                Console.Write($"\nPatient: {patient.mainDicomTags.PatientName}\n" +
                           $"PatientUID: {patient.ID}\n" +
                           $"PatientBirthday: {patient.mainDicomTags.Date}\n" +
                           $"PatientSex: {patient.mainDicomTags.PatientSex}\n" +
                           $"PatientID: {patient.mainDicomTags.PatientID}\n" +
                           $"PatientPath: {patient.Path}\n" +
                           $"Patient {index + 1}/{obj.Count}\n");
            }
            catch (Exception)
            {
                exceptions.Add($"ошибка в {url}/{obj[index]}");
            }
        }

        public static Patients ToString(ref Patients p)
        {
            if (p.mainDicomTags.Date != null && p.mainDicomTags.Date != "" && p.mainDicomTags.Date != " ")
            {
                p.mainDicomTags.Date = $"dd.MM.yyyy";
                return p;
            }
            else return p;
        }


Консоль netstat -ano
TCP    127.0.0.1:65092        127.0.0.1:8042         TIME_WAIT       0
  TCP    127.0.0.1:65094        127.0.0.1:8042         TIME_WAIT       0
  TCP    127.0.0.1:65095        127.0.0.1:8042         TIME_WAIT       0
  TCP    127.0.0.1:65096        127.0.0.1:8042         TIME_WAIT       0
  TCP    127.0.0.1:65097        127.0.0.1:8042         TIME_WAIT       0
  TCP    127.0.0.1:65102        127.0.0.1:8042         TIME_WAIT       0

И так далее...

При первом запуске все прекрасно и быстро парсится, но как я понял, у меня создается множество подключений с тайм-аутом в 240 секунд, что не дает далее нормально спарсить данные повторно. Отсюда и вопрос - как и через что реализовывать подобный парсинг, чтобы не приходилось по стольку времени ожидать повторной процедуры?
Скриншот фрагмента кода удалён модератором.
  • Вопрос задан
  • 423 просмотра
Подписаться 2 Простой 25 комментариев
Пригласить эксперта
Ответы на вопрос 1
1:
Не выбрасывай HttpClient после первой загрузки - используй его повторно для второй загрузки и далее.

2:
Ограничь количество параллельных запросов:
using var socketsHttpHandler = new SocketsHttpHandler()
{
	MaxConnectionsPerServer = 16
};
using var httpClient = new HttpClient(socketsHttpHandler);


3: Подумай о том, чтобы порефакторить свой код.
Можно, например, вместо ручного заполнения массивов из тасок - использовать Parallel.
+ Можно попробовать событийную модель через System.Threading.Channels.

Прям на корню убрать соединения в TIME_WAIT у тебя не выйдет. Можно только увеличить лимит сокетов или уменьшить время на уровне ОС, что делать не рекомендуется:
https://stackoverflow.com/questions/11569441/how-d...

Если программу нужно по несколько раз запускать подряд - лучше поменять код, чтобы не нужно было так делать и httpclient мог нормально переиспользовать уже открытые соединения.
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы