#c# #async-await #task
#c# #async-await #задача
Вопрос:
У меня возникли некоторые проблемы с поиском узкого места в приведенном ниже коде. Цель кода — отправить 400 запросов API асинхронно, чтобы получить данные как можно быстрее.
Проблема в том, что когда я запускаю один и тот же код синхронно, каждый запрос занимает около 3 секунд, но когда я запускаю его асинхронно, первые ответы занимают около 3 секунд, но время отклика постепенно увеличивается, а последние занимают более 20 секунд.
Я дважды пытался увеличить скорость своего интернета. В первый раз было отмечено значительное улучшение, но во второй раз практически никакого увеличения не произошло. Я поговорил с интернет-провайдером, и они объяснили, что увеличение скорости похоже на добавление полос к шоссе и что мне это пойдет на пользу, только если много других людей будут использовать то же соединение.
Я застрял на этой производительности или есть способ заставить эту программу работать быстрее?
using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Net.Http;
namespace ConsoleApplication___Alpha_Async_Download
{
class Program
{
public static int iterations = 400;
public static string[] content = new string[iterations];
public static string[] URL = new string[iterations];
public static int counter = 0;
public static int[] count = new int[iterations];
public static TimeSpan[] IndividualDownloadTime = new TimeSpan[iterations];
public static DateTime[] IndividualDownloadTimeStampB = new DateTime[iterations];
public static DateTime[] IndividualDownloadTimeStampA = new DateTime[iterations];
public static HttpClient client = new HttpClient();
static void Main(string[] args)
{
ServicePointManager.DefaultConnectionLimit = int.MaxValue;
Uri uri = new Uri("URL");
ServicePoint sp = ServicePointManager.FindServicePoint(uri);
sp.ConnectionLimit = int.MaxValue;
Console.WriteLine("Press ENTER to download data.");
ConsoleKeyInfo info = Console.ReadKey();
if (info.Key == ConsoleKey.Enter)
{
Console.Clear();
Data();
}
Console.ReadLine();
}
public static void Data()
{
Console.WriteLine("Downloading data...");
Console.WriteLine();
DateTime DownloadTimeStampA = DateTime.Now;
System.Collections.Generic.List<Task> tasks = new System.Collections.Generic.List<Task>();
for (int a = 0; a < iterations; a )
{
var local = a;
tasks.Add(Task.Run(async () =>
{
await DownloadDataAsync(local);
}));
}
Task.WaitAll(tasks.ToArray());
Console.WriteLine();
DateTime DownloadTimeStampB = DateTime.Now;
TimeSpan DownloadTime = DownloadTimeStampB - DownloadTimeStampA;
Console.WriteLine("Download Time: " DownloadTime.Minutes ":" DownloadTime.Seconds ":" DownloadTime.Milliseconds);
Console.WriteLine();
}
static async Task DownloadDataAsync(int c)
{
counter = counter 1;
count[c] = counter;
char[] delimiters = new char[] { ',' };
StreamReader URLs = new StreamReader(@"C:UsersOwnerDocumentsURL_List.csv");
string URLs2 = URLs.ReadLine();
string[] list = URLs2.Split(',');
List<string> URL = new List<string>(list);
IndividualDownloadTimeStampA[c] = DateTime.Now;
try
{
content[c] = await client.GetStringAsync(URL[c]);
}
catch (Exception ex)
{
Console.WriteLine(count[c] ". Error Message: " ex);
}
Console.WriteLine();
IndividualDownloadTimeStampB[c] = DateTime.Now;
IndividualDownloadTime[c] = IndividualDownloadTimeStampB[c] - IndividualDownloadTimeStampA[c];
Console.WriteLine(count[c] " Start: " IndividualDownloadTimeStampA[c].ToString("HH:mm:ss") " Finish: " IndividualDownloadTimeStampB[c].ToString("HH:mm:ss") " Duration: " IndividualDownloadTime[c].Minutes ":" IndividualDownloadTime[c].Seconds ":" IndividualDownloadTime[c].Milliseconds);
}
}
}
Комментарии:
1. Вы перенасыщаете
ThreadPool
, выполняя 400 задач одновременно. Увеличенная задержка связана с нехваткой доступных потоков в пуле.2. Я запустил программу, используя » postman-echo.com/delay/3 «в качестве URL-адреса-заполнителя и программа запустилась за 6,5 секунд, даже со встроенной задержкой в 3 секунды. Поэтому я не уверен, почему другой API занимает намного больше времени. Я связался с другим поставщиком API, и они сказали, что их задержка составляет микросекунды, поэтому я не думаю, что это проблема и с их стороны. Я предполагаю, что это связано с тем фактом, что API-заполнитель не передает столько данных. Но что я могу сделать, чтобы ускорить это?
3. Удалите
Task.Run
s. DownloadAsync уже является задачей — даже задачей на основе ввода-вывода — вам не нужно включать потоки, используемые Task.Run.4. Спасибо за предложение, я внес изменения, но, к сожалению, улучшения производительности не произошло. Это чище, хотя и без Task.Run.
5. Возможно, мне нужно обновить свой процессор? У меня четырехъядерный процессор с гиперпоточностью. Увеличит ли скорость 16-ядерного процессора с hyper-threading в четыре раза?
Ответ №1:
Как отмечали другие, проблема связана с Task.Run
, что не требуется в коде, привязанном к вводу-выводу. Заменить:
var local = a;
tasks.Add(Task.Run(async () =>
{
await DownloadDataAsync(local);
}));
с:
tasks.Add(DownloadDataAsync(a));
Кроме того, в DownloadDataAsync
есть много странного поведения; похоже, что он читает CSV-файл один раз за запрос. Было бы лучше просто загрузить его один раз и передавать определенный URL для каждого вызова DownloadDataAsync
. Также существует некоторое копирование в / из массивов и списков, которое не вызывает этой проблемы, но неэффективно и ненужно.
Комментарии:
1. Отличное предложение, я внес первое изменение, но, к сожалению, улучшения производительности не произошло. Для запуска все еще требовалось около 28 секунд. Я обязательно внесу предложение и относительно CSV-файла.
Ответ №2:
Во-первых, позвольте мне сказать, что ваша конфигурация для ServicePointManager.DefaultConnectionLimit слишком агрессивен, я понимаю, вы пытались это сделать, чтобы повысить производительность, но вы должны установить разумное ограничение для этого параметра, иначе вы столкнетесь с другими потенциальными проблемами в стеке tcp / ip.
Вы столкнулись с проблемой в пуле потоков, чтобы получить требуемую производительность, вы можете использовать следующий код для повышения производительности:
ThreadPool.SetMinThreads(100, 100); ThreadPool.SetMaxThreads(200, 200);
Эти настройки приведут к тому, что ThreadPool позволит быстрее создавать больше потоков и будет иметь больше портов асинхронного завершения, ограничивая также количество максимальных потоков, которые будут пытаться выполнять параллельную работу в любое время, прямо сейчас вы ограничиваетесь, и именно поэтому вы не получаете лучшей производительности, в частности, из-за настройки maxThreads, вот как объясняется в .Сетевая документация:
ThreadPool.Метод SetMaxThreads(Int32, Int32)
Задает количество запросов к пулу потоков, которые могут быть активны одновременно. Все запросы выше этого числа остаются в очереди до тех пор, пока потоки пула потоков не станут доступными.
Комментарии:
1. Спасибо за предложение, к сожалению, хотя улучшения производительности не произошло. Я последовал вашему совету и опустил ServicePointManager. DefaultConnectionLimit равным 40. Спасибо за совет!
2. если увеличение числа одновременно выполняющих работу потоков не помогает, скорее всего, проблема не в клиенте, а на стороне сервера, вероятно, там существует ограничение на количество одновременных вызовов с одного IP, это типичный сценарий для служб по предотвращению DoS-атак, ограничениями могут быть количество одновременных подключений или пропускная способность на IP-адрес. проверьте это.